new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/addrbook.jsm
@@ -0,0 +1,52 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailAddrbook"];
+
+/*
+ * Functionality related to the Thunderbird address book
+ *
+ */
+
+
+
+
+
+const ABMANAGER = "@mozilla.org/abmanager;1";
+
+var EnigmailAddrbook = {
+ /**
+ * Look up the address book card for a given email address
+ *
+ * @param emailAddr: String - email address to find
+ *
+ * @return if found: Object:
+ * - card: nsIAbCard for found email address
+ * - directory: nsIAbDirectory of found card
+ * NULL if not found
+ */
+ lookupEmailAddress: function(emailAddr) {
+ let abm = Cc[ABMANAGER].getService(Ci.nsIAbManager);
+ let enumerator = abm.directories;
+
+ while (enumerator.hasMoreElements()) {
+ let abd = enumerator.getNext().QueryInterface(Ci.nsIAbDirectory);
+ try {
+ let crd = abd.cardForEmailAddress(emailAddr);
+ if (crd) return {
+ directory: abd,
+ card: crd
+ };
+ }
+ catch (x) {}
+ }
+
+ return null;
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/amPrefsService.jsm
@@ -0,0 +1,92 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {
+ manager: Cm,
+ results: Cr,
+ Constructor: CC
+} = Components;
+Cm.QueryInterface(Ci.nsIComponentRegistrar);
+
+const EnigmailCompat = ChromeUtils.import("chrome://openpgp/content/modules/compat.jsm").EnigmailCompat;
+
+const CATEGORY = "mailnews-accountmanager-extensions";
+const CATEGORY_ENTRY = "openpgp-account-manager-extension";
+const PREF_SERVICE_NAME = "@mozilla.org/accountmanager/extension;1?name=enigprefs";
+
+var EXPORTED_SYMBOLS = ["EnigmailAmPrefsService"];
+
+var EnigmailAmPrefsService = {
+ startup: function(reason) {
+ try {
+ var catMan = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
+ catMan.addCategoryEntry(CATEGORY,
+ CATEGORY_ENTRY,
+ PREF_SERVICE_NAME,
+ false, true);
+ this.factory = new Factory(EnigmailPrefService);
+ }
+ catch (ex) {}
+ },
+
+ shutdown: function(reason) {
+ var catMan = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
+ catMan.deleteCategoryEntry(CATEGORY, CATEGORY_ENTRY, false);
+
+ if (this.factory) {
+ this.factory.unregister();
+ }
+ }
+};
+
+function EnigmailPrefService() {}
+
+EnigmailPrefService.prototype = {
+ name: "enigprefs",
+ chromePackageName: "openpgp",
+ classID: Components.ID("{f2be6d32-ff3c-11e9-8e8b-00163e5e6c00}"),
+ classDescription: "OpenPGP Account Manager Extension Service",
+ contractID: PREF_SERVICE_NAME,
+ QueryInterface: EnigmailCompat.generateQI(["nsIMsgAccountManagerExtension"]),
+
+ showPanel: function(server) {
+ // show Enigmail panel for POP3, IMAP, NNTP and "movemail" (unix) account types
+ switch (server.type) {
+ case "nntp":
+ case "imap":
+ case "pop3":
+ case "movemail":
+ return true;
+ }
+ return false;
+ }
+};
+
+class Factory {
+ constructor(component) {
+ this.component = component;
+ this.register();
+ Object.freeze(this);
+ }
+
+ createInstance(outer, iid) {
+ if (outer) {
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+ }
+ return new this.component();
+ }
+
+ register() {
+ Cm.registerFactory(this.component.prototype.classID,
+ this.component.prototype.classDescription,
+ this.component.prototype.contractID,
+ this);
+ }
+
+ unregister() {
+ Cm.unregisterFactory(this.component.prototype.classID, this);
+ }
+}
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/app.jsm
@@ -0,0 +1,71 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailApp"];
+
+const EnigmailLazy = ChromeUtils.import("chrome://openpgp/content/modules/lazy.jsm").EnigmailLazy;
+const getEnigmailLog = EnigmailLazy.loader("enigmail/log.jsm", "EnigmailLog");
+
+const DIR_SERV_CONTRACTID = "@mozilla.org/file/directory_service;1";
+const ENIG_EXTENSION_GUID = "{847b3a00-7ab1-11d4-8f02-006008948af5}";
+const XPCOM_APPINFO = "@mozilla.org/xre/app-info;1";
+
+var EnigmailApp = {
+ /**
+ * Platform application name (e.g. Thunderbird)
+ */
+ getName: function() {
+ return Cc[XPCOM_APPINFO].getService(Ci.nsIXULAppInfo).name;
+ },
+
+ /**
+ * Platform (Gecko) version number (e.g. 42.0)
+ * The platform version for SeaMonkey and for Thunderbird are identical
+ * (unlike the application version numbers)
+ */
+ getPlatformVersion: function() {
+ return Cc[XPCOM_APPINFO].getService(Ci.nsIXULAppInfo).platformVersion;
+ },
+
+ /**
+ * Return the directory holding the current profile as nsIFile object
+ */
+ getProfileDirectory: function() {
+ let ds = Cc[DIR_SERV_CONTRACTID].getService(Ci.nsIProperties);
+ return ds.get("ProfD", Ci.nsIFile);
+ },
+
+ /**
+ * Get Enigmail version
+ */
+ getVersion: function() {
+ getEnigmailLog().DEBUG("app.jsm: getVersion\n");
+ getEnigmailLog().DEBUG("app.jsm: installed version: " + EnigmailApp._version + "\n");
+ return EnigmailApp._version;
+ },
+
+ /**
+ * Get Enigmail installation directory
+ */
+ getInstallLocation: function() {
+ return EnigmailApp._installLocation;
+ },
+
+ setVersion: function(version) {
+ EnigmailApp._version = version;
+ },
+
+ setInstallLocation: function(location) {
+ EnigmailApp._installLocation = location;
+ },
+
+ initAddon: function(addon) {
+ EnigmailApp.setVersion(addon.version);
+ EnigmailApp.setInstallLocation(addon.installPath);
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/armor.jsm
@@ -0,0 +1,293 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailArmor"];
+
+const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+
+
+// Locates STRing in TEXT occurring only at the beginning of a line
+function indexOfArmorDelimiter(text, str, offset) {
+ let currentOffset = offset;
+
+ while (currentOffset < text.length) {
+ let loc = text.indexOf(str, currentOffset);
+
+ if (loc === -1 || loc === 0 || text.charAt(loc - 1) == "\n") {
+ return loc;
+ }
+
+ currentOffset = loc + str.length;
+ }
+
+ return -1;
+}
+
+function searchBlankLine(str, then) {
+ var offset = str.search(/\n\s*\r?\n/);
+ if (offset === -1) {
+ return "";
+ } else {
+ return then(offset);
+ }
+}
+
+function indexOfNewline(str, off, then) {
+ var offset = str.indexOf("\n", off);
+ if (offset === -1) {
+ return "";
+ } else {
+ return then(offset);
+ }
+}
+
+var EnigmailArmor = {
+ /**
+ * Locates offsets bracketing PGP armored block in text,
+ * starting from given offset, and returns block type string.
+ *
+ * @param text: String - ASCII armored text
+ * @param offset: Number - offset to start looking for block
+ * @param indentStr: String - prefix that is used for all lines (such as "> ")
+ * @param beginIndexObj: Object - o.value will contain offset of first character of block
+ * @param endIndexObj: Object - o.value will contain offset of last character of block (newline)
+ * @param indentStrObj: Object - o.value will contain indent of 1st line
+ *
+ * @return String - type of block found (e.g. MESSAGE, PUBLIC KEY)
+ * If no block is found, an empty String is returned;
+ */
+ locateArmoredBlock: function(text, offset, indentStr, beginIndexObj, endIndexObj, indentStrObj) {
+ EnigmailLog.DEBUG("armor.jsm: Enigmail.locateArmoredBlock: " + offset + ", '" + indentStr + "'\n");
+
+ beginIndexObj.value = -1;
+ endIndexObj.value = -1;
+
+ var beginIndex = indexOfArmorDelimiter(text, indentStr + "-----BEGIN PGP ", offset);
+
+ if (beginIndex == -1) {
+ var blockStart = text.indexOf("-----BEGIN PGP ");
+ if (blockStart >= 0) {
+ var indentStart = text.search(/\n.*-----BEGIN PGP /) + 1;
+ indentStrObj.value = text.substring(indentStart, blockStart);
+ indentStr = indentStrObj.value;
+ beginIndex = indexOfArmorDelimiter(text, indentStr + "-----BEGIN PGP ", offset);
+ }
+ }
+
+ if (beginIndex == -1)
+ return "";
+
+ // Locate newline at end of armor header
+ offset = text.indexOf("\n", beginIndex);
+
+ if (offset == -1)
+ return "";
+
+ var endIndex = indexOfArmorDelimiter(text, indentStr + "-----END PGP ", offset);
+
+ if (endIndex == -1)
+ return "";
+
+ // Locate newline at end of PGP block
+ endIndex = text.indexOf("\n", endIndex);
+
+ if (endIndex == -1) {
+ // No terminating newline
+ endIndex = text.length - 1;
+ }
+
+ var blockHeader = text.substr(beginIndex, offset - beginIndex + 1);
+
+ var blockRegex = new RegExp("^" + indentStr +
+ "-----BEGIN PGP (.{1,30})-----\\s*\\r?\\n");
+
+ var matches = blockHeader.match(blockRegex);
+
+ var blockType = "";
+ if (matches && (matches.length > 1)) {
+ blockType = matches[1];
+ EnigmailLog.DEBUG("armor.jsm: Enigmail.locateArmoredBlock: blockType=" + blockType + "\n");
+ }
+
+ if (blockType == "UNVERIFIED MESSAGE") {
+ // Skip any unverified message block
+ return EnigmailArmor.locateArmoredBlock(text, endIndex + 1, indentStr, beginIndexObj, endIndexObj, indentStrObj);
+ }
+
+ beginIndexObj.value = beginIndex;
+ endIndexObj.value = endIndex;
+
+ return blockType;
+ },
+
+ /**
+ * locateArmoredBlocks returns an array of ASCII Armor block positions
+ *
+ * @param text: String - text containing ASCII armored block(s)
+ *
+ * @return Array of objects with the following structure:
+ * obj.begin: Number
+ * obj.end: Number
+ * obj.indent: String
+ * obj.blocktype: String
+ *
+ * if no block was found, an empty array is returned
+ */
+ locateArmoredBlocks: function(text) {
+ var beginObj = {};
+ var endObj = {};
+ var indentStrObj = {};
+ var blocks = [];
+ var i = 0;
+ var b;
+
+ while ((b = EnigmailArmor.locateArmoredBlock(text, i, "", beginObj, endObj, indentStrObj)) !== "") {
+ blocks.push({
+ begin: beginObj.value,
+ end: endObj.value,
+ indent: indentStrObj.value ? indentStrObj.value : "",
+ blocktype: b
+ });
+
+ i = endObj.value;
+ }
+
+ EnigmailLog.DEBUG("armor.jsm: locateArmorBlocks: Found " + blocks.length + " Blocks\n");
+ return blocks;
+ },
+
+ extractSignaturePart: function(signatureBlock, part) {
+ EnigmailLog.DEBUG("armor.jsm: Enigmail.extractSignaturePart: part=" + part + "\n");
+
+ return searchBlankLine(signatureBlock, function(offset) {
+ return indexOfNewline(signatureBlock, offset + 1, function(offset) {
+ var beginIndex = signatureBlock.indexOf("-----BEGIN PGP SIGNATURE-----", offset + 1);
+ if (beginIndex == -1) {
+ return "";
+ }
+
+ if (part === EnigmailConstants.SIGNATURE_TEXT) {
+ return signatureBlock.substr(offset + 1, beginIndex - offset - 1).
+ replace(/^- -/, "-").
+ replace(/\n- -/g, "\n-").
+ replace(/\r- -/g, "\r-");
+ }
+
+ return indexOfNewline(signatureBlock, beginIndex, function(offset) {
+ var endIndex = signatureBlock.indexOf("-----END PGP SIGNATURE-----", offset);
+ if (endIndex == -1) {
+ return "";
+ }
+
+ var signBlock = signatureBlock.substr(offset, endIndex - offset);
+
+ return searchBlankLine(signBlock, function(armorIndex) {
+ if (part == EnigmailConstants.SIGNATURE_HEADERS) {
+ return signBlock.substr(1, armorIndex);
+ }
+
+ return indexOfNewline(signBlock, armorIndex + 1, function(armorIndex) {
+ if (part == EnigmailConstants.SIGNATURE_ARMOR) {
+ return signBlock.substr(armorIndex, endIndex - armorIndex).
+ replace(/\s*/g, "");
+ } else {
+ return "";
+ }
+ });
+ });
+ });
+ });
+ });
+ },
+
+ /**
+ * Remove all headers from an OpenPGP Armored message and replace them
+ * with a set of new headers.
+ *
+ * @param armorText: String - ASCII armored message
+ * @param headers: Object - key/value pairs of new headers to insert
+ *
+ * @return String - new armored message
+ */
+ replaceArmorHeaders: function(armorText, headers) {
+
+ let text = armorText.replace(/\r\n/g, "\n");
+ let i = text.search(/\n/);
+
+ if (i < 0) return armorText;
+ let m = text.substr(0, i + 1);
+
+ for (let j in headers) {
+ m += j + ": " + headers[j] + "\n";
+ }
+
+ i = text.search(/\n\n/);
+ if (i < 0) return armorText;
+ m += text.substr(i + 1);
+
+ return m;
+ },
+
+ /**
+ * Get a list of all headers found in an armor message
+ *
+ * @param text String - ASCII armored message
+ *
+ * @return Object: key/value pairs of headers. All keys are in lowercase.
+ */
+ getArmorHeaders: function(text) {
+ let headers = {};
+ let b = this.locateArmoredBlocks(text);
+
+ if (b.length === 0) {
+ return headers;
+ }
+
+ let msg = text.substr(b[0].begin);
+
+ let lx = new RegExp("\\n" + b[0].indent + "\\r?\\n");
+ let hdrEnd = msg.search(lx);
+ if (hdrEnd < 0) return headers;
+
+ let lines = msg.substr(0, hdrEnd).split(/\r?\n/);
+
+ let rx = new RegExp("^" + b[0].indent + "([^: ]+)(: )(.*)");
+ // skip 1st line (ARMOR-line)
+ for (let i = 1; i < lines.length; i++) {
+ let m = lines[i].match(rx);
+ if (m && m.length >= 4) {
+ headers[m[1].toLowerCase()] = m[3];
+ }
+ }
+
+ return headers;
+ },
+
+ /**
+ * Split armored blocks into an array of strings
+ */
+ splitArmoredBlocks: function(keyBlockStr) {
+ let myRe = /-----BEGIN PGP (PUBLIC|PRIVATE) KEY BLOCK-----/g;
+ let myArray;
+ let retArr = [];
+ let startIndex = -1;
+ while ((myArray = myRe.exec(keyBlockStr)) !== null) {
+ if (startIndex >= 0) {
+ let s = keyBlockStr.substring(startIndex, myArray.index);
+ retArr.push(s);
+ }
+ startIndex = myArray.index;
+ }
+
+ retArr.push(keyBlockStr.substring(startIndex));
+
+ return retArr;
+ }
+};
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/attachment.jsm
@@ -0,0 +1,19 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailAttachment"];
+
+const EnigmailCryptoAPI = ChromeUtils.import("chrome://openpgp/content/modules/cryptoAPI.jsm").EnigmailCryptoAPI;
+
+var EnigmailAttachment = {
+ getFileName: function(parent, byteData) {
+ const cApi = EnigmailCryptoAPI();
+ return cApi.sync(cApi.getFileName(parent, byteData));
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/autoSetup.jsm
@@ -0,0 +1,657 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/*eslint no-loop-func: 0 no-async-promise-executor: 0*/
+
+/**
+ * Module to determine the type of setup of the user, based on existing emails
+ * found in the inbox
+ */
+
+var EXPORTED_SYMBOLS = ["EnigmailAutoSetup"];
+
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+const EnigmailAutocrypt = ChromeUtils.import("chrome://openpgp/content/modules/autocrypt.jsm").EnigmailAutocrypt;
+const EnigmailFuncs = ChromeUtils.import("chrome://openpgp/content/modules/funcs.jsm").EnigmailFuncs;
+const EnigmailWindows = ChromeUtils.import("chrome://openpgp/content/modules/windows.jsm").EnigmailWindows;
+const EnigmailDialog = ChromeUtils.import("chrome://openpgp/content/modules/dialog.jsm").EnigmailDialog;
+const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
+const EnigmailKeyRing = ChromeUtils.import("chrome://openpgp/content/modules/keyRing.jsm").EnigmailKeyRing;
+const EnigmailMime = ChromeUtils.import("chrome://openpgp/content/modules/mime.jsm").EnigmailMime;
+const EnigmailCompat = ChromeUtils.import("chrome://openpgp/content/modules/compat.jsm").EnigmailCompat;
+const jsmime = ChromeUtils.import("resource:///modules/jsmime.jsm").jsmime;
+const EnigmailWks = ChromeUtils.import("chrome://openpgp/content/modules/webKey.jsm").EnigmailWks;
+const EnigmailTimer = ChromeUtils.import("chrome://openpgp/content/modules/timer.jsm").EnigmailTimer;
+const EnigmailStreams = ChromeUtils.import("chrome://openpgp/content/modules/streams.jsm").EnigmailStreams;
+const EnigmailGpg = ChromeUtils.import("chrome://openpgp/content/modules/gpg.jsm").EnigmailGpg;
+
+// Interfaces
+const nsIFolderLookupService = Ci.nsIFolderLookupService;
+const nsIMsgAccountManager = Ci.nsIMsgAccountManager;
+const nsIMsgAccount = Ci.nsIMsgAccount;
+const nsIMsgDBHdr = Ci.nsIMsgDBHdr;
+const nsIMessenger = Ci.nsIMessenger;
+const nsIMsgMessageService = Ci.nsIMsgMessageService;
+const nsIMsgFolder = Ci.nsIMsgFolder;
+
+/**
+ * the determined setup type
+ */
+var gDeterminedSetupType = {
+ value: EnigmailConstants.AUTOSETUP_NOT_INITIALIZED
+};
+
+var EnigmailAutoSetup = {
+
+ getDeterminedSetupType: async function() {
+ if (gDeterminedSetupType.value === EnigmailConstants.AUTOSETUP_NOT_INITIALIZED) {
+ return await this.determinePreviousInstallType();
+ }
+ else
+ return gDeterminedSetupType;
+ },
+
+ /**
+ * Identify which type of setup the user had before Enigmail was (re-)installed
+ *
+ * @return Promise<Object> with:
+ * - value : For each case assigned value, see EnigmailConstants.AUTOSETUP_xxx values
+ * - acSetupMessage {nsIMsgDBHdr} in case value === 1
+ * - msgHeaders {Object} in case value === 2
+ */
+ determinePreviousInstallType: function() {
+ let self = this;
+ gDeterminedSetupType = {
+ value: EnigmailConstants.AUTOSETUP_NOT_INITIALIZED
+ };
+
+ return new Promise(async (resolve, reject) => {
+ EnigmailLog.DEBUG("autoSetup.jsm: determinePreviousInstallType()\n");
+
+ try {
+ let msgAccountManager = Cc["@mozilla.org/messenger/account-manager;1"].getService(nsIMsgAccountManager);
+ let folderService = Cc["@mozilla.org/mail/folder-lookup;1"].getService(nsIFolderLookupService);
+ let returnMsgValue = {
+ value: EnigmailConstants.AUTOSETUP_NO_HEADER
+ };
+
+ var accounts = msgAccountManager.accounts;
+
+ let msgHeaders = [];
+ let autocryptSetupMessage = {};
+
+ // If no account, except Local Folders is configured
+ if (accounts.length <= 1) {
+ gDeterminedSetupType.value = EnigmailConstants.AUTOSETUP_NO_ACCOUNT;
+ resolve(gDeterminedSetupType);
+ return;
+ }
+
+ // Iterate through each account
+
+ for (var i = 0; i < accounts.length; i++) {
+ var account = accounts.queryElementAt(i, Ci.nsIMsgAccount);
+ var accountMsgServer = account.incomingServer;
+ EnigmailLog.DEBUG(`autoSetup.jsm: determinePreviousInstallType: scanning account "${accountMsgServer.prettyName}"\n`);
+
+ let msgFolderArr = [];
+
+ try {
+ getMsgFolders(account.incomingServer.rootFolder, msgFolderArr);
+ }
+ catch (e) {
+ EnigmailLog.DEBUG("autoSetup.jsm: determinePreviousInstallType: Error: " + e + "\n");
+ }
+
+ if (account.incomingServer.type.search(/^(none|nntp)$/) === 0) {
+ // ignore NNTP accounts and "Local Folders" accounts
+ continue;
+ }
+
+ // Iterating through each non empty Folder Database in the Account
+
+ for (var k = 0; k < msgFolderArr.length; k++) {
+ let msgFolder = msgFolderArr[k];
+ let msgDatabase = msgFolderArr[k].msgDatabase;
+
+ if ((msgFolder.flags & Ci.nsMsgFolderFlags.Junk) ||
+ (msgFolder.flags & Ci.nsMsgFolderFlags.Trash) ||
+ (!account.defaultIdentity)) {
+ continue;
+ }
+
+ EnigmailLog.DEBUG(`autoSetup.jsm: determinePreviousInstallType: scanning folder "${msgFolder.name}"\n`);
+
+ let msgEnumerator = msgDatabase.ReverseEnumerateMessages();
+
+ // Iterating through each message in the Folder
+ while (msgEnumerator.hasMoreElements()) {
+ let msgHeader = msgEnumerator.getNext().QueryInterface(nsIMsgDBHdr);
+ let msgURI = msgFolder.getUriForMsg(msgHeader);
+
+ let msgAuthor = "";
+ try {
+ msgAuthor = EnigmailFuncs.stripEmail(msgHeader.author);
+ }
+ catch (x) {}
+
+ // Listing all the headers in the message
+
+ let messenger = Components.classes["@mozilla.org/messenger;1"].createInstance(nsIMessenger);
+ let mms = messenger.messageServiceFromURI(msgURI).QueryInterface(nsIMsgMessageService);
+
+ let headerObj = await getStreamedHeaders(msgURI, mms);
+ let checkHeaderValues = await checkHeaders(headerObj, msgHeader, msgAuthor, account.defaultIdentity.email, msgFolder, returnMsgValue, msgHeaders);
+
+ msgHeaders = checkHeaderValues.msgHeaders;
+ returnMsgValue = checkHeaderValues.returnMsgValue;
+
+ const currDateInSeconds = getCurrentTime();
+ const diffSecond = currDateInSeconds - msgHeader.dateInSeconds;
+
+ /**
+ 2592000 = No. of Seconds in a Month.
+ This is to ignore 1 month old messages.
+ */
+ if (diffSecond > 2592000.0) {
+ break;
+ }
+ }
+
+ }
+
+ }
+ if (returnMsgValue.acSetupMessage) {
+ EnigmailLog.DEBUG(`autoSetup.jsm: determinePreviousInstallType: found AC-Setup message\n`);
+ gDeterminedSetupType = returnMsgValue;
+ resolve(gDeterminedSetupType);
+ }
+ else {
+ EnigmailLog.DEBUG(`msgHeaders.length: ${msgHeaders.length}\n`);
+
+ // find newest message to know the protocol
+ let latestMsg = null;
+ for (let i = 0; i < msgHeaders.length; i++) {
+ if (!latestMsg) {
+ latestMsg = msgHeaders[i];
+ }
+
+ if (msgHeaders[i].dateTime > latestMsg.dateTime) {
+ latestMsg = msgHeaders[i];
+ }
+ }
+
+ if (latestMsg) {
+ if (latestMsg.msgType === "Autocrypt") {
+ returnMsgValue.value = EnigmailConstants.AUTOSETUP_AC_HEADER;
+ returnMsgValue.msgHeaders = msgHeaders;
+ }
+ else {
+ returnMsgValue.value = EnigmailConstants.AUTOSETUP_ENCRYPTED_MSG;
+ returnMsgValue.msgHeaders = msgHeaders;
+ }
+ }
+
+ let defId = EnigmailFuncs.getDefaultIdentity();
+ if (defId) {
+ returnMsgValue.userName = defId.fullName;
+ returnMsgValue.userEmail = defId.email;
+ }
+ else {
+ returnMsgValue.userName = undefined;
+ returnMsgValue.userEmail = undefined;
+ }
+
+ gDeterminedSetupType = returnMsgValue;
+ EnigmailLog.DEBUG(`autoSetup.jsm: determinePreviousInstallType: found type: ${returnMsgValue.value}\n`);
+ resolve(returnMsgValue);
+ }
+ }
+ catch (x) {
+ reject(x);
+ }
+ });
+
+ },
+
+ /**
+ * Process the Autocrypt Setup Message
+ *
+ * @param {Object} headerValue: contains header and attachment of an Autocrypt Setup Message
+ * @param {nsIWindow} passwordWindow: parent window for password dialog
+ * @param {nsIWindow} confirmWindow: parent window for confirmation dialog
+ * (note: split into 2 parent windows for unit tests)
+ *
+ * @return {Promise<Number>}: Import result.
+ * 1: imported OK
+ * 0: no Autocrypt setup message
+ * -1: import not OK (wrong password, canceled etc.)
+ */
+
+ performAutocryptSetup: async function(headerValue, passwordWindow = null, confirmWindow = null) {
+ EnigmailLog.DEBUG("autoSetup.jsm: performAutocryptSetup()\n");
+
+ EnigmailDialog.alert(window, "EnigmailAutocrypt.handleBackupMessage not implemented");
+
+ let imported = 0;
+ /*
+ if (headerValue.attachment.contentType.search(/^application\/autocrypt-setup$/i) === 0) {
+ try {
+ let res = await EnigmailAutocrypt.getSetupMessageData(headerValue.attachment.url);
+ let passwd = EnigmailWindows.autocryptSetupPasswd(passwordWindow, "input", res.passphraseFormat, res.passphraseHint);
+
+ if ((!passwd) || passwd == "") {
+ throw "noPasswd";
+ }
+
+ // TODO: await EnigmailAutocrypt.handleBackupMessage(passwd, res.attachmentData, headerValue.acSetupMessage.author);
+ EnigmailDialog.info(confirmWindow, EnigmailLocale.getString("autocrypt.importSetupKey.success", headerValue.acSetupMessage.author));
+ imported = 1;
+ }
+ catch (err) {
+ EnigmailLog.DEBUG("autoSetup.jsm: performAutocryptSetup got cancel status=" + err + "\n");
+ imported = -1;
+
+ switch (err) {
+ case "getSetupMessageData":
+ EnigmailDialog.alert(confirmWindow, EnigmailLocale.getString("autocrypt.importSetupKey.invalidMessage"));
+ break;
+ case "wrongPasswd":
+ if (EnigmailDialog.confirmDlg(confirmWindow, EnigmailLocale.getString("autocrypt.importSetupKey.wrongPasswd"), EnigmailLocale.getString("dlg.button.retry"),
+ EnigmailLocale.getString("dlg.button.cancel"))) {
+ EnigmailAutoSetup.performAutocryptSetup(headerValue);
+ }
+ break;
+ case "keyImportFailed":
+ EnigmailDialog.alert(confirmWindow, EnigmailLocale.getString("autocrypt.importSetupKey.invalidKey"));
+ break;
+ default:
+ EnigmailDialog.alert(confirmWindow, EnigmailLocale.getString("keyserver.error.unknown"));
+ }
+ }
+ }
+ */
+
+ return imported;
+ },
+
+ /**
+ * Process accounts with Autocrypt headers
+ *
+ * @param {Object} setupType: containing Autocrypt headers from accounts
+ *
+ * @return {Promise<Number>}: Result: 0: OK / 1: failure
+ */
+
+ processAutocryptHeader: function(setupType) {
+ EnigmailLog.DEBUG("autoSetup.jsm: processAutocryptHeader()\n");
+
+ return new Promise(async (resolve, reject) => {
+
+ // find newest message to know the protocol
+ let latestMsg = null;
+ for (let i = 0; i < setupType.msgHeaders.length; i++) {
+ if (!latestMsg) {
+ latestMsg = setupType.msgHeaders[i];
+ }
+
+ if (setupType.msgHeaders[i].dateTime > latestMsg) {
+ latestMsg = setupType.msgHeaders[i];
+ }
+ }
+
+ let sysType = latestMsg.msgType;
+ EnigmailLog.DEBUG(`autoSetup.jsm: processAutocryptHeader: got type: ${sysType}\n`);
+
+
+ for (let i = 0; i < setupType.msgHeaders.length; i++) {
+ if (setupType.msgHeaders[i].msgType === "Autocrypt") {
+ // FIXME
+ let success = await EnigmailAutocrypt.processAutocryptHeader(setupType.msgHeaders[i].fromAddr, [setupType.msgHeaders[i].msgData],
+ setupType.msgHeaders[i].date);
+ if (success !== 0) {
+ resolve(1);
+ }
+ }
+ }
+ resolve(0);
+ });
+ },
+
+ /**
+ * Create a new autocrypt key for every configured account and configure the account
+ * to use that key. The keys are not protected by a password.
+ *
+ * The creation is done in the background after waiting timeoutValue ms
+ * @param {Number} timeoutValue: number of miliseconds to wait before starting
+ * the process
+ */
+ createKeyForAllAccounts: function(timeoutValue = 1000) {
+ EnigmailLog.DEBUG("autoSetup.jsm: createKeyForAllAccounts()\n");
+ let self = this;
+
+ EnigmailTimer.setTimeout(async function _f() {
+ let msgAccountManager = Cc["@mozilla.org/messenger/account-manager;1"].getService(nsIMsgAccountManager);
+ let accounts = msgAccountManager.accounts;
+ let createdKeys = [];
+
+ for (let i = 0; i < accounts.length; i++) {
+ let account = accounts.queryElementAt(i, Ci.nsIMsgAccount);
+ let id = account.defaultIdentity;
+
+ if (id && id.email) {
+ let keyId = await self.createAutocryptKey(id.fullName, id.email);
+ EnigmailLog.DEBUG(`autoSetup.jsm: createKeyForAllAccounts: created key ${keyId}\n`);
+ if (keyId) {
+ let keyObj = EnigmailKeyRing.getKeyById(keyId);
+ if (keyObj) createdKeys.push(keyObj);
+ id.setBoolAttribute("enablePgp", true);
+ id.setCharAttribute("pgpkeyId", keyId);
+ id.setIntAttribute("pgpKeyMode", 1);
+ id.setBoolAttribute("pgpMimeMode", true);
+ id.setBoolAttribute("pgpSignEncrypted", true);
+ }
+ }
+ }
+
+ // upload created keys to WKD (if possible)
+ EnigmailWks.wksUpload(createdKeys, null);
+ }, timeoutValue);
+ },
+
+ /**
+ * Create a new autocrypt-complinant key
+ * The keys will not be protected by passwords.
+ *
+ * @param {String} userName: Display name
+ * @param {String} userEmail: Email address
+ *
+ * @return {Promise<Boolean>}: Success (true = successful)
+ */
+ createAutocryptKey: function(userName, userEmail) {
+ return new Promise((resolve, reject) => {
+ EnigmailLog.DEBUG("autoSetup.jsm: createAutocryptKey()\n");
+
+ let keyType = "ECC",
+ keyLength = 0;
+
+ if (!EnigmailGpg.getGpgFeature("supports-ecc-keys")) {
+ // fallback for gpg < 2.1
+ keyLength = 4096;
+ keyType = "RSA";
+ }
+
+ let expiry = 1825, // 5 years
+ passphrase = "",
+ generateObserver = {
+ keyId: null,
+ backupLocation: null,
+ _state: 0,
+
+ onDataAvailable: function(data) {},
+ onStopRequest: function(exitCode) {
+ EnigmailLog.DEBUG("autoSetup.jsm: createAutocryptKey(): key generation complete\n");
+ resolve(generateObserver.keyId);
+ }
+ };
+
+ try {
+ let keygenRequest = EnigmailKeyRing.generateKey(userName, "", userEmail, expiry, keyLength, keyType, passphrase, generateObserver);
+ }
+ catch (ex) {
+ EnigmailLog.DEBUG("autoSetup.jsm: createAutocryptKey: error: " + ex.message);
+ resolve(null);
+ }
+ });
+ },
+
+ /**
+ * Configure Enigmail to use existing keys
+ */
+ applyExistingKeys: function() {
+ let msgAccountManager = Cc["@mozilla.org/messenger/account-manager;1"].getService(nsIMsgAccountManager);
+ let identities = msgAccountManager.allIdentities;
+
+ for (let i = 0; i < identities.length; i++) {
+ let id = identities.queryElementAt(i, Ci.nsIMsgIdentity);
+
+ if (id && id.email) {
+ let keyObj = EnigmailKeyRing.getSecretKeyByEmail(id.email);
+ if (keyObj) {
+ EnigmailLog.DEBUG(`autoSetup.jsm: applyExistingKeys: found key ${keyObj.keyId}\n`);
+ id.setBoolAttribute("enablePgp", true);
+ id.setCharAttribute("pgpkeyId", "0x" + keyObj.fpr);
+ id.setIntAttribute("pgpKeyMode", 1);
+ id.setBoolAttribute("pgpMimeMode", true);
+ id.setBoolAttribute("pgpSignEncrypted", true);
+ }
+ }
+ }
+ }
+};
+
+
+/**
+ * Recusrively go through all folders to get a flat array of all sub-folders
+ * starting with a parent folder.
+ *
+ * @param {nsIMsgFolder} folder: the folder to scan
+ * @param {nsIMsgFolder} msgFolderArr: An array to be filled with all folders that contain messages
+ */
+
+function getMsgFolders(folder, msgFolderArr) {
+ if (folder.getTotalMessages(false) > 0) {
+ msgFolderArr.push(folder);
+ }
+
+ // add all subfolders
+ if (folder.hasSubFolders) {
+ let subFolders = folder.subFolders;
+ while (subFolders.hasMoreElements()) {
+ getMsgFolders(subFolders.getNext().QueryInterface(nsIMsgFolder), msgFolderArr);
+ }
+ }
+}
+
+// Util Function for Extracting manually added Headers
+function streamListener(callback) {
+ let streamListener = {
+ mAttachments: [],
+ mHeaders: [],
+ mBusy: true,
+
+ onStartRequest: function(aRequest) {
+ this.mAttachments = [];
+ this.mHeaders = [];
+ this.mBusy = true;
+
+ var channel = aRequest.QueryInterface(Components.interfaces.nsIChannel);
+ channel.URI.QueryInterface(Components.interfaces.nsIMsgMailNewsUrl);
+ channel.URI.msgHeaderSink = this; // adds this header sink interface to the channel
+ },
+ onStopRequest: function(aRequest, aStatusCode) {
+ callback();
+ this.mBusy = false; // if needed, you can poll this var to see if we are done collecting attachment details
+ },
+ onDataAvailable: function(aRequest, aInputStream, aOffset, aCount) {},
+ onStartHeaders: function() {},
+ onEndHeaders: function() {},
+ processHeaders: function(aHeaderNameEnumerator, aHeaderValueEnumerator, aDontCollectAddress) {
+ while (aHeaderNameEnumerator.hasMore()) {
+ this.mHeaders.push({
+ name: aHeaderNameEnumerator.getNext().toLowerCase(),
+ value: aHeaderValueEnumerator.getNext()
+ });
+ }
+ },
+ handleAttachment: function(aContentType, aUrl, aDisplayName, aUri, aIsExternalAttachment) {
+ if (aContentType == "text/html") {
+ return;
+ }
+ this.mAttachments.push({
+ contentType: aContentType,
+ url: aUrl,
+ displayName: aDisplayName,
+ uri: aUri,
+ isExternal: aIsExternalAttachment
+ });
+ },
+ onEndAllAttachments: function() {},
+ onEndMsgDownload: function(aUrl) {},
+ onEndMsgHeaders: function(aUrl) {},
+ onMsgHasRemoteContent: function(aMsgHdr) {},
+ getSecurityInfo: function() {},
+ setSecurityInfo: function(aSecurityInfo) {},
+ getDummyMsgHeader: function() {},
+
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Components.interfaces.nsIStreamListener) ||
+ aIID.equals(Components.interfaces.nsIMsgHeaderSink) ||
+ aIID.equals(Components.interfaces.nsISupports)) {
+ return this;
+ }
+
+ throw Components.results.NS_NOINTERFACE;
+ }
+ };
+
+ return streamListener;
+}
+
+function getStreamedMessage(msgFolder, msgHeader) {
+ return new Promise((resolve, reject) => {
+ let msgURI = msgFolder.getUriForMsg(msgHeader);
+ var listener = streamListener(() => {
+ resolve(listener.mAttachments[0]);
+ });
+ let messenger = Components.classes["@mozilla.org/messenger;1"].createInstance(nsIMessenger);
+ let mms = messenger.messageServiceFromURI(msgURI).QueryInterface(nsIMsgMessageService);
+ mms.streamMessage(msgURI, listener, null, null, true, "filter");
+ });
+}
+
+function checkHeaders(headerObj, msgHeader, msgAuthor, accountEmail, msgFolder, returnMsgValue, msgHeaders) {
+ return new Promise(async (resolve, reject) => {
+ if (headerObj['autocrypt-setup-message'] && msgHeader.author == msgHeader.recipients) {
+
+ // To extract Attachement for Autocrypt Setup Message
+
+ returnMsgValue.attachment = await getStreamedMessage(msgFolder, msgHeader);
+
+ if (!returnMsgValue.acSetupMessage) {
+ returnMsgValue.value = 1;
+ returnMsgValue.acSetupMessage = msgHeader;
+ }
+ else if (returnMsgValue.acSetupMessage.date < msgHeader.date) {
+ returnMsgValue.acSetupMessage = msgHeader;
+ }
+
+ }
+ else if (msgAuthor == accountEmail &&
+ ("autocrypt" in headerObj)) {
+
+ let msgType = "Autocrypt";
+
+ let fromHeaderExist = null;
+ for (let j = 0; j < msgHeaders.length; j++) {
+ if (msgHeaders[j].fromAddr == msgAuthor) {
+ fromHeaderExist = msgHeaders[j];
+ break;
+ }
+ }
+
+ if (fromHeaderExist === null) {
+ let dateTime = new Date(0);
+ try {
+ dateTime = jsmime.headerparser.parseDateHeader(headerObj.date);
+ }
+ catch (x) {}
+
+ let addHeader = {
+ fromAddr: msgAuthor,
+ msgType: msgType,
+ msgData: headerObj.autocrypt,
+ date: headerObj.date,
+ dateTime: dateTime
+ };
+ msgHeaders.push(addHeader);
+ }
+ else {
+ let dateTime = new Date(0);
+ try {
+ dateTime = jsmime.headerparser.parseDateHeader(headerObj.date);
+ }
+ catch (x) {}
+ if (dateTime > fromHeaderExist.dateTime) {
+ fromHeaderExist.msgData = headerObj.autocrypt;
+ fromHeaderExist.date = headerObj.date;
+ fromHeaderExist.msgType = msgType;
+ fromHeaderExist.dateTime = dateTime;
+ }
+ }
+ }
+
+ resolve({
+ 'returnMsgValue': returnMsgValue,
+ 'msgHeaders': msgHeaders
+ });
+ });
+}
+
+function getStreamedHeaders(msgURI, mms) {
+
+ return new Promise((resolve, reject) => {
+ let headers = Cc["@mozilla.org/messenger/mimeheaders;1"].createInstance(Ci.nsIMimeHeaders);
+ let headerObj = {};
+ try {
+ mms.streamHeaders(msgURI, EnigmailStreams.newStringStreamListener(aRawString => {
+ try {
+ //EnigmailLog.DEBUG(`getStreamedHeaders: ${aRawString}\n`);
+ headers.initialize(aRawString);
+
+ let i = headers.headerNames;
+ while (i.hasMore()) {
+ let hdrName = i.getNext().toLowerCase();
+
+ let hdrValue = headers.extractHeader(hdrName, true);
+ headerObj[hdrName] = hdrValue;
+ }
+
+ if ("autocrypt" in headerObj) {
+ let acHeader = headers.extractHeader("autocrypt", false);
+ acHeader = acHeader.replace(/keydata=/i, 'keydata="') + '"';
+
+ let paramArr = EnigmailMime.getAllParameters(acHeader);
+ paramArr.keydata = paramArr.keydata.replace(/[\r\n\t ]/g, "");
+
+ headerObj.autocrypt = "";
+ for (i in paramArr) {
+ if (headerObj.autocrypt.length > 0) {
+ headerObj.autocrypt += "; ";
+ }
+ headerObj.autocrypt += `${i}="${paramArr[i]}"`;
+ }
+ }
+ }
+ catch (e) {
+ reject({});
+ EnigmailLog.DEBUG("autoSetup.jsm: getStreamedHeaders: Error: " + e + "\n");
+ }
+ resolve(headerObj);
+ }), null, false);
+ }
+ catch (e) {
+ reject({});
+ EnigmailLog.DEBUG("autoSetup.jsm: getStreamedHeaders: Error: " + e + "\n");
+ }
+ });
+}
+
+
+function getCurrentTime() {
+ return new Date().getTime() / 1000;
+}
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/autocrypt.jsm
@@ -0,0 +1,1077 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * Module for dealing with received Autocrypt headers, level 0
+ * See details at https://github.com/mailencrypt/autocrypt
+ */
+
+var EXPORTED_SYMBOLS = ["EnigmailAutocrypt"];
+
+const Cr = Components.results;
+
+Components.utils.importGlobalProperties(["crypto"]);
+
+const jsmime = ChromeUtils.import("resource:///modules/jsmime.jsm").jsmime;
+const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+const EnigmailFuncs = ChromeUtils.import("chrome://openpgp/content/modules/funcs.jsm").EnigmailFuncs;
+const EnigmailMime = ChromeUtils.import("chrome://openpgp/content/modules/mime.jsm").EnigmailMime;
+const EnigmailSqliteDb = ChromeUtils.import("chrome://openpgp/content/modules/sqliteDb.jsm").EnigmailSqliteDb;
+const PromiseUtils = ChromeUtils.import("resource://gre/modules/PromiseUtils.jsm").PromiseUtils;
+const EnigmailTimer = ChromeUtils.import("chrome://openpgp/content/modules/timer.jsm").EnigmailTimer;
+const EnigmailKey = ChromeUtils.import("chrome://openpgp/content/modules/key.jsm").EnigmailKey;
+const EnigmailKeyRing = ChromeUtils.import("chrome://openpgp/content/modules/keyRing.jsm").EnigmailKeyRing;
+const EnigmailOpenPGP = ChromeUtils.import("chrome://openpgp/content/modules/openpgp.jsm").EnigmailOpenPGP;
+const EnigmailRNG = ChromeUtils.import("chrome://openpgp/content/modules/rng.jsm").EnigmailRNG;
+const EnigmailSend = ChromeUtils.import("chrome://openpgp/content/modules/send.jsm").EnigmailSend;
+const EnigmailStreams = ChromeUtils.import("chrome://openpgp/content/modules/streams.jsm").EnigmailStreams;
+const EnigmailArmor = ChromeUtils.import("chrome://openpgp/content/modules/armor.jsm").EnigmailArmor;
+const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
+const EnigmailRules = ChromeUtils.import("chrome://openpgp/content/modules/rules.jsm").EnigmailRules;
+const EnigmailKeyEditor = ChromeUtils.import("chrome://openpgp/content/modules/keyEditor.jsm").EnigmailKeyEditor;
+const EnigmailStdlib = ChromeUtils.import("chrome://openpgp/content/modules/stdlib.jsm").EnigmailStdlib;
+const EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs.jsm").EnigmailPrefs;
+const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
+const EnigmailCryptoAPI = ChromeUtils.import("chrome://openpgp/content/modules/cryptoAPI.jsm").EnigmailCryptoAPI;
+
+var gCreatedSetupIds = [];
+
+var EnigmailAutocrypt = {
+ /**
+ * Process the "Autocrypt:" header and if successful store the update in the database
+ *
+ * @param {String} fromAddr: Address of sender (From: header)
+ * @param {Array of String} headerDataArr: all instances of the Autocrypt: header found in the message
+ * @param {String or Number} dateSent: "Date:" field of the message as readable string or in seconds after 1970-01-01
+ * @param {Boolean} autoCryptEnabled: if true, autocrypt is enabled for the context of the message
+ *
+ * @return {Promise<Number>}: success: 0 = success, 1+ = failure
+ */
+ processAutocryptHeader: async function(fromAddr, headerDataArr, dateSent, autoCryptEnabled = false, isGossip = false) {
+ EnigmailLog.DEBUG("autocrypt.jsm: processAutocryptHeader(): from=" + fromAddr + "\n");
+ let conn;
+
+ try {
+ // critical parameters: {param: mandatory}
+ const CRITICAL = {
+ addr: true,
+ keydata: true,
+ type: false, // That's actually oboslete according to the Level 1 spec.
+ "prefer-encrypt": false
+ };
+
+ try {
+ fromAddr = EnigmailFuncs.stripEmail(fromAddr).toLowerCase();
+ }
+ catch (ex) {
+ throw "processAutocryptHeader error " + ex;
+ }
+ let foundTypes = {};
+ let paramArr = [];
+
+ for (let hdrNum = 0; hdrNum < headerDataArr.length; hdrNum++) {
+
+ let hdr = headerDataArr[hdrNum].replace(/[\r\n \t]/g, "");
+ let k = hdr.search(/keydata=/);
+ if (k > 0) {
+ let d = hdr.substr(k);
+ if (d.search(/"/) < 0) {
+ hdr = hdr.replace(/keydata=/, 'keydata="') + '"';
+ }
+ }
+
+ paramArr = EnigmailMime.getAllParameters(hdr);
+
+ for (let i in CRITICAL) {
+ if (CRITICAL[i]) {
+ // found mandatory parameter
+ if (!(i in paramArr)) {
+ EnigmailLog.DEBUG("autocrypt.jsm: processAutocryptHeader: cannot find param '" + i + "'\n");
+ return 1; // do nothing if not all mandatory parts are present
+ }
+ }
+ }
+
+ for (let i in paramArr) {
+ if (i.substr(0, 1) !== "_") {
+ if (!(i in CRITICAL)) {
+ EnigmailLog.DEBUG("autocrypt.jsm: processAutocryptHeader: unknown critical param " + i + "\n");
+ return 2; // do nothing if an unknown critical parameter is found
+ }
+ }
+ }
+
+ paramArr.addr = paramArr.addr.toLowerCase();
+
+ if (fromAddr !== paramArr.addr) {
+ EnigmailLog.DEBUG("autocrypt.jsm: processAutocryptHeader: from Addr " + fromAddr + " != " + paramArr.addr.toLowerCase() + "\n");
+
+ return 3;
+ }
+
+ if (!("type" in paramArr)) {
+ paramArr.type = (isGossip ? "1g" : "1");
+ }
+ else {
+ paramArr.type = paramArr.type.toLowerCase();
+ if (paramArr.type !== "1") {
+ EnigmailLog.DEBUG("autocrypt.jsm: processAutocryptHeader: unknown type " + paramArr.type + "\n");
+ return 4; // we currently only support 1 (=OpenPGP)
+ }
+ }
+
+ try {
+ let keyData = atob(paramArr.keydata);
+ }
+ catch (ex) {
+ EnigmailLog.DEBUG("autocrypt.jsm: processAutocryptHeader: key is not base64-encoded\n");
+ return 5;
+ }
+
+ if (paramArr.type in foundTypes) {
+ EnigmailLog.DEBUG("autocrypt.jsm: processAutocryptHeader: duplicate header for type=" + paramArr.type + "\n");
+ return 6; // do not process anything if more than one Autocrypt header for the same type is found
+ }
+
+ foundTypes[paramArr.type] = 1;
+ }
+
+ if (isGossip) {
+ paramArr["prefer-encrypt"] = "nopreference";
+ }
+
+ if (!("prefer-encrypt" in paramArr)) {
+ paramArr["prefer-encrypt"] = "nopreference";
+ }
+
+ let lastDate;
+ if (typeof dateSent === "string") {
+ lastDate = jsmime.headerparser.parseDateHeader(dateSent);
+ }
+ else {
+ lastDate = new Date(dateSent * 1000);
+ }
+ let now = new Date();
+ if (lastDate > now) {
+ lastDate = now;
+ }
+ paramArr.dateSent = lastDate;
+
+ if (("_enigmail_artificial" in paramArr) && (paramArr._enigmail_artificial === "yes")) {
+ if ("_enigmail_fpr" in paramArr) {
+ paramArr.fpr = paramArr._enigmail_fpr;
+ }
+
+ paramArr.keydata = "";
+ paramArr.autocryptDate = 0;
+ }
+ else {
+ paramArr.autocryptDate = lastDate;
+ }
+
+ try {
+ conn = await EnigmailSqliteDb.openDatabase();
+ }
+ catch (ex) {
+ EnigmailLog.DEBUG("autocrypt.jsm: processAutocryptHeader: could not open database\n");
+ return 7;
+ }
+
+ let resultObj = await findUserRecord(conn, [fromAddr], paramArr.type);
+ EnigmailLog.DEBUG("autocrypt.jsm: got " + resultObj.numRows + " rows\n");
+ if (resultObj.data.length === 0) {
+ await appendUser(conn, paramArr);
+ }
+ else {
+ await updateUser(conn, paramArr, resultObj.data, autoCryptEnabled);
+ }
+
+ EnigmailLog.DEBUG("autocrypt.jsm: OK - closing connection\n");
+ conn.close();
+ return 0;
+ }
+ catch (err) {
+ EnigmailLog.DEBUG("autocrypt.jsm: error - closing connection: " + err + "\n");
+ conn.close();
+ return 8;
+ }
+ },
+
+ /**
+ * Import autocrypt OpenPGP keys into regular keyring for a given list of email addresses
+ * @param {Array of String} emailAddr: email addresses
+ * @param {Boolean} acceptGossipKeys: import keys received via gossip
+ *
+ * @return {Promise<Array of keyObj>}
+ */
+ importAutocryptKeys: async function(emailAddr, acceptGossipKeys = false) {
+ EnigmailLog.DEBUG("autocrypt.jsm: importAutocryptKeys()\n");
+
+ let keyArr = await this.getOpenPGPKeyForEmail(emailAddr);
+ if (!keyArr) return [];
+
+ let importedKeys = [];
+ let now = new Date();
+ let prev = null;
+
+ for (let i = 0; i < keyArr.length; i++) {
+ if (prev && prev.email === keyArr[i].email && prev.type === "1" && keyArr[i].type === "1g") {
+ // skip if we have "gossip" key preceeded by a "regular" key
+ continue;
+ }
+ if (!acceptGossipKeys && keyArr[i].type === "1g") {
+ EnigmailLog.DEBUG(`autocrypt.jsm: importAutocryptKeys: skipping gossip key for ${keyArr[i].email}\n`);
+ continue;
+ }
+
+ prev = keyArr[i];
+ if ((now - keyArr[i].lastAutocrypt) / (1000 * 60 * 60 * 24) < 366) {
+ // only import keys received less than 12 months ago
+ try {
+ let keyData = atob(keyArr[i].keyData);
+ if (keyData.length > 1) {
+ importedKeys = await this.applyKeyFromKeydata(keyData, keyArr[i].email, keyArr[i].state, keyArr[i].type);
+ }
+ }
+ catch (ex) {
+ EnigmailLog.DEBUG("autocrypt.jsm importAutocryptKeys: exception " + ex.toString() + "\n");
+ }
+ }
+ }
+
+ return importedKeys;
+ },
+
+ /**
+ * Import given key data and set the per-recipient rule accordingly
+ *
+ * @param {String} keyData - String key data (BLOB, binary form)
+ * @param {String} email - email address associated with key
+ * @param {String} autocryptState - mutual or nopreference
+ * @param {String} type - autocrypt header type (1 / 1g)
+ *
+ * @return {Promise<Array of keys>} list of imported keys
+ */
+ applyKeyFromKeydata: async function(keyData, email, autocryptState, type) {
+ let keysObj = {};
+ let importedKeys = [];
+
+ let pubkey = EnigmailOpenPGP.enigmailFuncs.bytesToArmor(EnigmailOpenPGP.armor.public_key, keyData);
+ await EnigmailKeyRing.importKeyAsync(null, false, pubkey, "", {}, keysObj);
+
+ if (keysObj.value) {
+ importedKeys = importedKeys.concat(keysObj.value);
+
+ if (keysObj.value.length > 0) {
+ let key = EnigmailKeyRing.getKeyById(keysObj.value[0]);
+
+ // enable encryption if state (prefer-encrypt) is "mutual";
+ // otherwise, disable it explicitely
+ let signEncrypt = (autocryptState === "mutual" ? 1 : 0);
+
+ if (key && key.fpr) {
+ let ruleObj = {
+ email: `{${EnigmailConstants.AC_RULE_PREFIX}${email}}`,
+ keyList: `0x${key.fpr}`,
+ sign: signEncrypt,
+ encrypt: signEncrypt,
+ pgpMime: 2,
+ flags: 0
+ };
+
+ EnigmailRules.insertOrUpdateRule(ruleObj);
+ await this.setKeyImported(null, email);
+ }
+ }
+ }
+
+ return importedKeys;
+ },
+
+ /**
+ * Update key in the Autocrypt database to mark it "imported in keyring"
+ */
+ setKeyImported: async function(connection, email) {
+ EnigmailLog.DEBUG(`autocrypt.jsm: setKeyImported(${email})\n`);
+ try {
+ let conn = connection;
+ if (!conn) {
+ conn = await EnigmailSqliteDb.openDatabase();
+ }
+ let updateStr = "update autocrypt_keydata set keyring_inserted = '1' where email = :email;";
+
+ let updateObj = {
+ email: email.toLowerCase()
+ };
+
+ await new Promise((resolve, reject) =>
+ conn.executeTransaction(function _trx() {
+ conn.execute(updateStr, updateObj).then(r => {
+ resolve(r);
+ }).catch(err => {
+ EnigmailLog.DEBUG(`autocrypt.jsm: setKeyImported: error ${err}\n`);
+ reject(err);
+ });
+ }));
+
+ if (!connection) conn.close();
+ }
+ catch (err) {
+ EnigmailLog.DEBUG(`autocrypt.jsm: setKeyImported: error ${err}\n`);
+ throw err;
+ }
+ },
+
+ /**
+ * Go through all emails in the autocrypt store and determine which keys already
+ * have a per-recipient rule
+ */
+ updateAllImportedKeys: async function() {
+ EnigmailLog.DEBUG(`autocrypt.jsm: updateAllImportedKeys()\n`);
+ try {
+ let conn = await EnigmailSqliteDb.openDatabase();
+
+ let rows = [];
+ await conn.execute("select email, type from autocrypt_keydata where type = '1';", {},
+ function _onRow(record) {
+ rows.push(record.getResultByName("email"));
+ });
+
+ for (let i in rows) {
+ let r = EnigmailRules.getRuleByEmail(`${EnigmailConstants.AC_RULE_PREFIX}${rows[i]}`);
+ if (r) {
+ await this.setKeyImported(conn, rows[i], "1");
+ }
+
+ }
+ EnigmailLog.DEBUG(`autocrypt.jsm: updateAllImportedKeys done\n`);
+
+ conn.close();
+ }
+ catch (err) {
+ EnigmailLog.DEBUG(`autocrypt.jsm: updateAllImportedKeys: error ${err}\n`);
+ throw err;
+ }
+ },
+
+ /**
+ * Find an autocrypt OpenPGP key for a given list of email addresses
+ * @param emailAddr: Array of String - email addresses
+ *
+ * @return Promise(<Array of Object>)
+ * Object: {fpr, keyData, lastAutocrypt}
+ */
+ getOpenPGPKeyForEmail: function(emailAddr) {
+ EnigmailLog.DEBUG("autocrypt.jsm: getOpenPGPKeyForEmail(" + emailAddr.join(",") + ")\n");
+
+ let conn;
+
+ return new Promise((resolve, reject) => {
+ EnigmailSqliteDb.openDatabase().then(
+ function onConnection(connection) {
+ conn = connection;
+ return findUserRecord(conn, emailAddr, "1,1g");
+ },
+ function onError(error) {
+ EnigmailLog.DEBUG("autocrypt.jsm: getOpenPGPKeyForEmail: could not open database\n");
+ reject("getOpenPGPKeyForEmail1 error " + error);
+ }
+ ).then(
+ function gotData(resultObj) {
+ EnigmailLog.DEBUG("autocrypt.jsm: getOpenPGPKeyForEmail got " + resultObj.numRows + " rows\n");
+ conn.close();
+
+ if (resultObj.data.length === 0) {
+ resolve(null);
+ }
+ else {
+ let retArr = [];
+ for (let i in resultObj.data) {
+ let record = resultObj.data[i];
+ retArr.push({
+ email: record.getResultByName("email"),
+ fpr: record.getResultByName("fpr"),
+ keyData: record.getResultByName("keydata"),
+ state: record.getResultByName("state"),
+ type: record.getResultByName("type"),
+ lastAutocrypt: new Date(record.getResultByName("last_seen_autocrypt"))
+ });
+ }
+
+ resolve(retArr);
+ }
+ }
+ ).
+ catch((err) => {
+ conn.close();
+ reject("getOpenPGPKeyForEmail: error " + err);
+ });
+ });
+ },
+
+ /**
+ * Create Autocrypt Setup Message
+ *
+ * @param identity: Object - nsIMsgIdentity
+ *
+ * @return Promise({str, passwd}):
+ * msg: String - complete setup message
+ * passwd: String - backup password
+ */
+ // needs rewrite, OpenPGP.js not available
+ /*
+ createSetupMessage: function(identity) {
+ EnigmailLog.DEBUG("autocrypt.jsm: createSetupMessage()\n");
+
+ return new Promise((resolve, reject) => {
+ let keyId = "";
+ let key;
+ try {
+
+ if (!EnigmailCore.getService(null, false)) {
+ reject(0);
+ return;
+ }
+
+ if (identity.getIntAttribute("pgpKeyMode") === 1) {
+ keyId = identity.getCharAttribute("pgpkeyId");
+ }
+
+ if (keyId.length > 0) {
+ key = EnigmailKeyRing.getKeyById(keyId);
+ }
+ else {
+ key = EnigmailKeyRing.getSecretKeyByUserId(identity.email);
+ }
+
+ if (!key) {
+ EnigmailLog.DEBUG("autocrypt.jsm: createSetupMessage: no key found for " + identity.email + "\n");
+ reject(1);
+ return;
+ }
+
+ let keyData = key.getSecretKey(true).keyData;
+
+ if (!keyData || keyData.length === 0) {
+ EnigmailLog.DEBUG("autocrypt.jsm: createSetupMessage: no key found for " + identity.email + "\n");
+ reject(1);
+ return;
+ }
+
+ let ac = EnigmailFuncs.getAccountForIdentity(identity);
+ let preferEncrypt = ac.incomingServer.getIntValue("acPreferEncrypt") > 0 ? "mutual" : "nopreference";
+
+ let innerMsg = EnigmailArmor.replaceArmorHeaders(keyData, {
+ 'Autocrypt-Prefer-Encrypt': preferEncrypt
+ }) + '\r\n';
+
+ let bkpCode = createBackupCode();
+ let enc = {
+ // TODO: message: EnigmailOpenPGP.openpgp.message.fromText(innerMsg),
+ passwords: bkpCode,
+ armor: true
+ };
+
+ // create symmetrically encrypted message
+ // TODO: EnigmailOpenPGP.openpgp.encrypt(enc).then(msg => {
+ let msgData = EnigmailArmor.replaceArmorHeaders(msg.data, {
+ 'Passphrase-Format': 'numeric9x4',
+ 'Passphrase-Begin': bkpCode.substr(0, 2)
+ }).replace(/\n/g, "\r\n");
+
+ let m = createBackupOuterMsg(identity.email, msgData);
+ resolve({
+ msg: m,
+ passwd: bkpCode
+ });
+ }).catch(e => {
+ EnigmailLog.DEBUG("autocrypt.jsm: createSetupMessage: error " + e + "\n");
+ reject(2);
+ });
+ }
+ catch (ex) {
+ EnigmailLog.DEBUG("autocrypt.jsm: createSetupMessage: error " + ex.toString() + "\n");
+ reject(4);
+ }
+ });
+ },
+ */
+
+ /**
+ * Create and send the Autocrypt Setup Message to yourself
+ * The message is sent asynchronously.
+ *
+ * @param identity: Object - nsIMsgIdentity
+ *
+ * @return Promise(passwd):
+ * passwd: String - backup password
+ *
+ */
+ /*
+ sendSetupMessage: function(identity) {
+ EnigmailLog.DEBUG("autocrypt.jsm: sendSetupMessage()\n");
+
+ let self = this;
+ return new Promise((resolve, reject) => {
+ self.createSetupMessage(identity).then(res => {
+ let composeFields = Cc["@mozilla.org/messengercompose/composefields;1"].createInstance(Ci.nsIMsgCompFields);
+ composeFields.characterSet = "UTF-8";
+ composeFields.messageId = EnigmailRNG.generateRandomString(27) + "-enigmail";
+ composeFields.from = identity.email;
+ composeFields.to = identity.email;
+ gCreatedSetupIds.push(composeFields.messageId);
+
+ let now = new Date();
+ let mimeStr = "Message-Id: " + composeFields.messageId + "\r\n" +
+ "Date: " + now.toUTCString() + "\r\n" + res.msg;
+
+ if (EnigmailSend.sendMessage(mimeStr, composeFields, null)) {
+ resolve(res.passwd);
+ }
+ else {
+ reject(99);
+ }
+ });
+ });
+ },
+ */
+
+
+ /**
+ * get the data of the attachment of a setup message
+ *
+ * @param attachmentUrl: String - URL of the attachment
+ *
+ * @return Promise(Object):
+ * attachmentData: String - complete attachment data
+ * passphraseFormat: String - extracted format from the header (e.g. numeric9x4) [optional]
+ * passphraseHint: String - 1st two digits of the password [optional]
+ */
+ getSetupMessageData: function(attachmentUrl) {
+ EnigmailLog.DEBUG("autocrypt.jsm: getSetupMessageData()\n");
+
+ return new Promise((resolve, reject) => {
+ let s = EnigmailStreams.newStringStreamListener(data => {
+ let start = {},
+ end = {};
+ let msgType = EnigmailArmor.locateArmoredBlock(data, 0, "", start, end, {});
+
+ if (msgType === "MESSAGE") {
+ EnigmailLog.DEBUG("autocrypt.jsm: getSetupMessageData: got backup key\n");
+ let armorHdr = EnigmailArmor.getArmorHeaders(data);
+
+ let passphraseFormat = "generic";
+ if ("passphrase-format" in armorHdr) {
+ passphraseFormat = armorHdr["passphrase-format"];
+ }
+ let passphraseHint = "";
+ if ("passphrase-begin" in armorHdr) {
+ passphraseHint = armorHdr["passphrase-begin"];
+ }
+
+ resolve({
+ attachmentData: data,
+ passphraseFormat: passphraseFormat,
+ passphraseHint: passphraseHint
+ });
+ }
+ else {
+ reject("getSetupMessageData");
+ }
+ });
+
+ let channel = EnigmailStreams.createChannel(attachmentUrl);
+ channel.asyncOpen(s, null);
+ });
+ },
+
+ /**
+ * @return Promise(Object):
+ * fpr: String - FPR of the imported key
+ * preferEncrypt: String - Autocrypt preferEncrypt value (e.g. mutual)
+ */
+ // needs rewrite, OpenPGP.js not available
+ /*
+ handleBackupMessage: function(passwd, attachmentData, fromAddr) {
+ EnigmailLog.DEBUG("autocrypt.jsm: handleBackupMessage()\n");
+
+ return new Promise((resolve, reject) => {
+ let start = {},
+ end = {};
+ let msgType = EnigmailArmor.locateArmoredBlock(attachmentData, 0, "", start, end, {});
+
+ // TODO: EnigmailOpenPGP.openpgp.message.readArmored(attachmentData.substring(start.value, end.value)).then(encMessage => {
+ let enc = {
+ message: encMessage,
+ passwords: [passwd],
+ format: 'utf8'
+ };
+
+ // TODO: return EnigmailOpenPGP.openpgp.decrypt(enc);
+ })
+ .then(msg => {
+ EnigmailLog.DEBUG("autocrypt.jsm: handleBackupMessage: data: " + msg.data.length + "\n");
+
+ let setupData = importSetupKey(msg.data);
+ if (setupData) {
+ EnigmailKeyEditor.setKeyTrust(null, "0x" + setupData.fpr, "5", function(returnCode) {
+ if (returnCode === 0) {
+ let id = EnigmailStdlib.getIdentityForEmail(EnigmailFuncs.stripEmail(fromAddr).toLowerCase());
+ let ac = EnigmailFuncs.getAccountForIdentity(id.identity);
+ ac.incomingServer.setBoolValue("enableAutocrypt", true);
+ ac.incomingServer.setIntValue("acPreferEncrypt", (setupData.preferEncrypt === "mutual" ? 1 : 0));
+ id.identity.setCharAttribute("pgpkeyId", "0x" + setupData.fpr);
+ id.identity.setBoolAttribute("enablePgp", true);
+ id.identity.setBoolAttribute("pgpSignEncrypted", true);
+ id.identity.setBoolAttribute("pgpMimeMode", true);
+ id.identity.setIntAttribute("pgpKeyMode", 1);
+ resolve(setupData);
+ }
+ else {
+ reject("keyImportFailed");
+ }
+ });
+ }
+ else {
+ reject("keyImportFailed");
+ }
+ }).
+ catch(err => {
+ reject("wrongPasswd");
+ });
+ });
+ },
+ */
+
+ /**
+ * Determine if a message id was self-created (only during same TB session)
+ */
+ isSelfCreatedSetupMessage: function(messageId) {
+ return (gCreatedSetupIds.indexOf(messageId) >= 0);
+ },
+
+ /**
+ * Check if an account is set up with OpenPGP and if the configured key is valid
+ *
+ * @param emailAddr: String - email address identifying the account
+ *
+ * @return Boolean: true: account is valid / false: OpenPGP not configured or key not valid
+ */
+ isAccountSetupForPgp: function(emailAddr) {
+ let id = EnigmailStdlib.getIdentityForEmail(EnigmailFuncs.stripEmail(emailAddr).toLowerCase());
+ let keyObj = null;
+
+ if (!(id && id.identity)) return false;
+ if (!id.identity.getBoolAttribute("enablePgp")) return false;
+
+ if (id.identity.getIntAttribute("pgpKeyMode") === 1) {
+ keyObj = EnigmailKeyRing.getKeyById(id.identity.getCharAttribute("pgpkeyId"));
+ }
+ else {
+ keyObj = EnigmailKeyRing.getSecretKeyByUserId(emailAddr);
+ }
+
+ if (!keyObj) return false;
+ if (!keyObj.secretAvailable) return false;
+
+ let o = keyObj.getEncryptionValidity();
+ if (!o.keyValid) return false;
+ o = keyObj.getSigningValidity();
+ if (!o.keyValid) return false;
+
+ return true;
+ },
+
+ /**
+ * Delete the record for a user from the autocrypt keystore
+ * The record with the highest precedence is deleted (i.e. type=1 before type=1g)
+ */
+ deleteUser: async function(email, type) {
+ EnigmailLog.DEBUG(`autocrypt.jsm: deleteUser(${email})\n`);
+ let conn = await EnigmailSqliteDb.openDatabase();
+
+ let updateStr = "delete from autocrypt_keydata where email = :email and type = :type";
+ let updateObj = {
+ email: email,
+ type: type
+ };
+
+ await new Promise((resolve, reject) => {
+ conn.executeTransaction(function _trx() {
+ conn.execute(updateStr, updateObj).then(
+ function _ok() {
+ resolve();
+ }
+ ).catch(function _err() {
+ reject("update failed");
+ });
+ });
+ });
+ EnigmailLog.DEBUG(" deletion complete\n");
+
+ conn.close();
+ }
+
+};
+
+/**
+ * Find the database record for a given email address and type
+ *
+ * @param connection: Object - SQLite connection
+ * @param emails Array of String - Email addresses to search
+ * @param type: String - types to search (in lowercase), separated by comma
+ *
+ * @return {Promise<Object>}:
+ * numRows: number of results
+ * data: array of RowObject. Query columns using data[i].getResultByName(columnName);
+ */
+async function findUserRecord(connection, emails, type) {
+ EnigmailLog.DEBUG("autocrypt.jsm: findUserRecord\n");
+
+ let data = [];
+ let t = type.split(/[ ,]+/);
+
+ let queryParam = {
+ e0: emails[0],
+ t0: t[0]
+ };
+
+ let numRows = 0;
+
+ let search = ":e0";
+ for (let i = 1; i < emails.length; i++) {
+ search += ", :e" + i;
+ queryParam["e" + i] = emails[i].toLowerCase();
+ }
+
+ let typeParam = ":t0";
+ for (let i = 1; i < t.length; i++) {
+ typeParam += ", :t" + i;
+ queryParam["t" + i] = t[i];
+ }
+
+ try {
+ await connection.execute(
+ "select * from autocrypt_keydata where email in (" + search + ") and type in (" + typeParam + ") order by email, type", queryParam,
+ function _onRow(row) {
+ EnigmailLog.DEBUG("autocrypt.jsm: findUserRecord - got row\n");
+ data.push(row);
+ ++numRows;
+ });
+ }
+ catch (x) {
+ EnigmailLog.DEBUG(`autocrypt.jsm: findUserRecord: error ${x}\n`);
+ throw x;
+ }
+
+ return {
+ data: data,
+ numRows: numRows
+ };
+}
+
+/**
+ * Create new database record for an Autorypt header
+ *
+ * @param connection: Object - SQLite connection
+ * @param paramsArr: Object - the Autocrypt header parameters
+ *
+ * @return Promise
+ */
+async function appendUser(connection, paramsArr) {
+ EnigmailLog.DEBUG("autocrypt.jsm: appendUser(" + paramsArr.addr + ")\n");
+
+ if (!("fpr" in paramsArr)) {
+ await getFprForKey(paramsArr);
+ }
+
+ return new Promise((resolve, reject) => {
+
+ if (paramsArr.autocryptDate == 0) {
+ // do not insert record for non-autocrypt mail
+ resolve();
+ return;
+ }
+
+ connection.executeTransaction(function _trx() {
+ connection.execute("insert into autocrypt_keydata (email, keydata, fpr, type, last_seen_autocrypt, last_seen, state) values " +
+ "(:email, :keyData, :fpr, :type, :lastAutocrypt, :lastSeen, :state)", {
+ email: paramsArr.addr.toLowerCase(),
+ keyData: paramsArr.keydata,
+ fpr: ("fpr" in paramsArr ? paramsArr.fpr : ""),
+ type: paramsArr.type,
+ lastAutocrypt: paramsArr.dateSent.toJSON(),
+ lastSeen: paramsArr.dateSent.toJSON(),
+ state: paramsArr["prefer-encrypt"]
+ }).then(
+ function _ok() {
+ EnigmailLog.DEBUG("autocrypt.jsm: appendUser - OK\n");
+ resolve();
+ }
+ ).catch(function _err() {
+ reject("appendUser");
+ });
+ });
+ });
+}
+
+/**
+ * Update the record for an email address and type, if the email we got is newer
+ * than the latest record we already stored
+ *
+ * @param connection: Object - SQLite connection
+ * @param paramsArr: Object - the Autocrypt header parameters
+ * @param resultRows: Array of mozIStorageRow - records stored in the database
+ * @param autoCryptEnabled: Boolean: is autocrypt enabled for this transaction
+ *
+ * @return Promise
+ */
+async function updateUser(connection, paramsArr, resultRows, autoCryptEnabled) {
+ EnigmailLog.DEBUG("autocrypt.jsm: updateUser\n");
+
+ let currData = resultRows[0];
+ let deferred = PromiseUtils.defer();
+
+ let lastSeen = new Date(currData.getResultByName("last_seen"));
+ let lastAutocrypt = new Date(currData.getResultByName("last_seen_autocrypt"));
+ let notGossip = (currData.getResultByName("state") !== "gossip");
+ let currentKeyData = currData.getResultByName("keydata");
+ let isKeyInKeyring = (currData.getResultByName("keyring_inserted") === "1");
+
+ if (lastSeen >= paramsArr.dateSent ||
+ (paramsArr["prefer-encrypt"] === "gossip" && notGossip)) {
+ EnigmailLog.DEBUG("autocrypt.jsm: updateUser: not a relevant new latest message\n");
+
+ return;
+ }
+
+ EnigmailLog.DEBUG("autocrypt.jsm: updateUser: updating latest message\n");
+
+ let updateStr;
+ let updateObj;
+
+ if (paramsArr.autocryptDate > 0) {
+ lastAutocrypt = paramsArr.autocryptDate;
+ if (!("fpr" in paramsArr)) {
+ await getFprForKey(paramsArr);
+ }
+
+ updateStr = "update autocrypt_keydata set state = :state, keydata = :keyData, last_seen_autocrypt = :lastAutocrypt, " +
+ "fpr = :fpr, last_seen = :lastSeen where email = :email and type = :type";
+ updateObj = {
+ email: paramsArr.addr.toLowerCase(),
+ state: paramsArr["prefer-encrypt"],
+ keyData: paramsArr.keydata,
+ fpr: ("fpr" in paramsArr ? paramsArr.fpr : ""),
+ type: paramsArr.type,
+ lastAutocrypt: lastAutocrypt.toJSON(),
+ lastSeen: paramsArr.dateSent.toJSON()
+ };
+ }
+ else {
+ updateStr = "update autocrypt_keydata set state = :state, last_seen = :lastSeen where email = :email and type = :type";
+ updateObj = {
+ email: paramsArr.addr.toLowerCase(),
+ state: paramsArr["prefer-encrypt"],
+ type: paramsArr.type,
+ lastSeen: paramsArr.dateSent.toJSON()
+ };
+ }
+
+ if (!("fpr" in paramsArr)) {
+ await getFprForKey(paramsArr);
+ }
+
+ await new Promise((resolve, reject) => {
+ connection.executeTransaction(function _trx() {
+ connection.execute(updateStr, updateObj).then(
+ function _ok() {
+ resolve();
+ }
+ ).catch(function _err() {
+ reject("update failed");
+ });
+ });
+ });
+
+ if (autoCryptEnabled && isKeyInKeyring && (currentKeyData !== paramsArr.keydata)) {
+ await updateKeyIfNeeded(paramsArr.addr.toLowerCase(), paramsArr.keydata, paramsArr.fpr, paramsArr.type, paramsArr["prefer-encrypt"]);
+ }
+
+ return;
+}
+
+
+/**
+ * Determine if a key in the keyring should be replaced by a new (or updated) key
+ * @param {String} email - Email address
+ * @param {String} keydata - new keydata to import
+ * @param {String} fpr - fingerprint of new key
+ * @param {String} keyType - key type (1 / 1g)
+ * @param {String} autocryptState - mutual or nopreference
+ *
+ * @return {Promise<Boolean>} - key updated
+ */
+async function updateKeyIfNeeded(email, keydata, fpr, keyType, autocryptState) {
+ let ruleNode = EnigmailRules.getRuleByEmail(EnigmailConstants.AC_RULE_PREFIX + email);
+ if (!ruleNode) return false;
+
+ let doImport = false;
+
+ let currentKeyId = ruleNode.getAttribute("keyList");
+ if (`0x${fpr}` === currentKeyId || keyType === "1") {
+ doImport = true;
+ }
+ else {
+ // Gossip keys
+ let keyObj = EnigmailKeyRing.getKeyById(currentKeyId);
+ let encOk = keyObj.getEncryptionValidity().keyValid;
+
+ if (!encOk) {
+ // current key is not valid anymore
+ doImport = true;
+ }
+ }
+
+ if (doImport) {
+ await EnigmailAutocrypt.applyKeyFromKeydata(atob(keydata), email, autocryptState, keyType);
+ }
+
+ return doImport;
+}
+
+/**
+ * Set the fpr attribute for a given key parameter object
+ */
+async function getFprForKey(paramsArr) {
+ let keyData = atob(paramsArr.keydata);
+
+ const cApi = EnigmailCryptoAPI();
+
+ try {
+ let keyInfo = await cApi.getKeyListFromKeyBlock(keyData);
+
+ // keyInfo is an object, not an array => convert to array 1st
+ let keyArr = [];
+
+ for (let k in keyInfo) {
+ keyArr.push(keyInfo[k]);
+ }
+
+ if (keyArr.length === 1) {
+ paramsArr.fpr = keyArr[0].fpr;
+ }
+ }
+ catch (x) {}
+}
+
+
+/**
+ * Create the 9x4 digits backup code as defined in the Autocrypt spec
+ *
+ * @return String: xxxx-xxxx-...
+ */
+
+function createBackupCode() {
+ let bkpCode = "";
+
+ for (let i = 0; i < 9; i++) {
+ if (i > 0) bkpCode += "-";
+
+ let a = new Uint8Array(4);
+ crypto.getRandomValues(a);
+ for (let j = 0; j < 4; j++) {
+ bkpCode += String(a[j] % 10);
+ }
+ }
+ return bkpCode;
+}
+
+
+function createBackupOuterMsg(toEmail, encryptedMsg) {
+
+ let boundary = EnigmailMime.createBoundary();
+
+ let msgStr = 'To: ' + toEmail + '\r\n' +
+ 'From: ' + toEmail + '\r\n' +
+ 'Autocrypt-Setup-Message: v1\r\n' +
+ 'Subject: ' + EnigmailLocale.getString("autocrypt.setupMsg.subject") + '\r\n' +
+ 'Content-type: multipart/mixed; boundary="' + boundary + '"\r\n\r\n' +
+ '--' + boundary + '\r\n' +
+ 'Content-Type: text/plain\r\n\r\n' +
+ EnigmailLocale.getString("autocryptSetupReq.setupMsg.desc") + '\r\n\r\n' +
+ EnigmailLocale.getString("autocrypt.setupMsg.msgBody") + '\r\n\r\n' +
+ EnigmailLocale.getString("autocryptSetupReq.setupMsg.backup") + '\r\n' +
+ '--' + boundary + '\r\n' +
+ 'Content-Type: application/autocrypt-setup\r\n' +
+ 'Content-Disposition: attachment; filename="autocrypt-setup-message.html"\r\n\r\n' +
+ '<html><body>\r\n' +
+ '<p>' + EnigmailLocale.getString("autocrypt.setupMsg.fileTxt") + '</p>\r\n' +
+ '<pre>\r\n' +
+ encryptedMsg +
+ '</pre></body></html>\r\n' +
+ '--' + boundary + '--\r\n';
+
+ return msgStr;
+}
+
+
+/**
+ * @return Object:
+ * fpr: String - FPR of the imported key
+ * preferEncrypt: String - Autocrypt preferEncrypt value (e.g. mutual)
+ */
+function importSetupKey(keyData) {
+
+ EnigmailLog.DEBUG("autocrypt.jsm: importSetupKey()\n");
+
+ let preferEncrypt = "nopreference"; // Autocrypt default according spec
+ let start = {},
+ end = {},
+ keyObj = {};
+
+ let msgType = EnigmailArmor.locateArmoredBlock(keyData, 0, "", start, end, {});
+ if (msgType === "PRIVATE KEY BLOCK") {
+
+ let headers = EnigmailArmor.getArmorHeaders(keyData);
+ if ("autocrypt-prefer-encrypt" in headers) {
+ preferEncrypt = headers["autocrypt-prefer-encrypt"];
+ }
+
+ let r = EnigmailKeyRing.importKey(null, false, keyData, "", {}, keyObj);
+
+ if (r === 0 && keyObj.value && keyObj.value.length > 0) {
+ return {
+ fpr: keyObj.value[0],
+ preferEncrypt: preferEncrypt
+ };
+ }
+ }
+
+ return null;
+}
+
+
+function updateRuleForEmail(email, preferEncrypt, fpr = null) {
+ let node = EnigmailRules.getRuleByEmail(EnigmailConstants.AC_RULE_PREFIX + email);
+
+ if (node) {
+ let signEncrypt = (preferEncrypt === "mutual" ? "1" : "0");
+
+ if (node.getAttribute("sign") !== signEncrypt ||
+ node.getAttribute("encrypt") !== signEncrypt) {
+
+ node.setAttribute("sign", signEncrypt);
+ node.setAttribute("encrypt", signEncrypt);
+ if (fpr) {
+ node.setAttribute("keyList", `0x${fpr}`);
+ }
+ EnigmailRules.saveRulesFile();
+ }
+ }
+}
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/buildDate.jsm
@@ -0,0 +1,3 @@
+"use strict";
+var EXPORTED_SYMBOLS = ["EnigmailBuildDate"];
+var EnigmailBuildDate = { built: "20191029-1710" };
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/card.jsm
@@ -0,0 +1,33 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailCard"];
+
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailExecution = ChromeUtils.import("chrome://openpgp/content/modules/execution.jsm").EnigmailExecution;
+const EnigmailGpg = ChromeUtils.import("chrome://openpgp/content/modules/gpg.jsm").EnigmailGpg;
+
+var EnigmailCard = {
+ getCardStatus: function(exitCodeObj, errorMsgObj) {
+ EnigmailLog.DEBUG("card.jsm: EnigmailCard.getCardStatus\n");
+ const GPG_ADDITIONAL_OPTIONS=["--no-verbose", "--status-fd", "2", "--fixed-list-mode", "--with-colons", "--card-status"];
+ const args = EnigmailGpg.getStandardArgs(false).concat(GPG_ADDITIONAL_OPTIONS);
+ const statusMsgObj = {};
+ const statusFlagsObj = {};
+
+ const outputTxt = EnigmailExecution.execCmd(EnigmailGpg.agentPath, args, "", exitCodeObj, statusFlagsObj, statusMsgObj, errorMsgObj);
+
+ if ((exitCodeObj.value === 0) && !outputTxt) {
+ exitCodeObj.value = -1;
+ return "";
+ }
+
+ return outputTxt;
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/clipboard.jsm
@@ -0,0 +1,105 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailClipboard"];
+
+// Import the Services module for future use, if we're not in
+// a browser window where it's already loaded.
+const Services = ChromeUtils.import('resource://gre/modules/Services.jsm').Services;
+
+
+// Create a constructor for the built-in supports-string class.
+const nsSupportsString = Components.Constructor("@mozilla.org/supports-string;1", "nsISupportsString");
+
+function SupportsString(str) {
+ // Create an instance of the supports-string class
+ var res = nsSupportsString();
+
+ // Store the JavaScript string that we want to wrap in the new nsISupportsString object
+ res.data = str;
+ return res;
+}
+
+// Create a constructor for the built-in transferable class
+const nsTransferable = Components.Constructor("@mozilla.org/widget/transferable;1", "nsITransferable");
+
+// Create a wrapper to construct an nsITransferable instance and set its source to the given window, when necessary
+function Transferable(source) {
+ let res = nsTransferable();
+ if ('init' in res) {
+ // When passed a Window object, find a suitable privacy context for it.
+ if (source instanceof Ci.nsIDOMWindow)
+ source = source.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation);
+
+ res.init(source);
+ }
+ return res;
+}
+
+var EnigmailClipboard = {
+
+ /**
+ * Get the content string of a clipboard
+ *
+ * @param window : nsIWindow or nsIDOMWindow of caller
+ * @param clipBoardType: Number - clipboard type according to nsIClipboard
+ *
+ * @return String - content of clipBoard
+ */
+
+ getClipboardContent: function(window, clipBoardType) {
+ if (!window) throw "erorr - window must not be null";
+
+ let clipBoard = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
+ let data = {};
+ let cBoardContent = "";
+
+ if (clipBoardType !== clipBoard.kSelectionClipboard || clipBoard.supportsSelectionClipboard()) {
+ try {
+ let transferable = Transferable(window);
+ transferable.addDataFlavor("text/unicode");
+ clipBoard.getData(transferable, clipBoardType);
+ let flavour = {};
+ let length = {};
+ transferable.getAnyTransferData(flavour, data, length);
+ cBoardContent = data.value.QueryInterface(Ci.nsISupportsString).data;
+ }
+ catch (ex) {}
+ }
+ return cBoardContent;
+ },
+
+ /**
+ * Set the global (and if available, the selection clipboard)
+ *
+ * @param str: String - data to set
+ * @param clipBoardType: Number - clipboard type according to nsIClipboard.
+ * If not provided, the global plus the selection clipboard will be used
+ *
+ * @return Boolean: true - success / false - failure
+ */
+ setClipboardContent: function(str, clipBoardType) {
+ let useClipboard = clipBoardType;
+ if (clipBoardType === undefined) {
+ useClipboard = Ci.nsIClipboard.kGlobalClipboard;
+ }
+ try {
+ let clipBoard = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
+ let clipBoardHlp = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
+ clipBoardHlp.copyStringToClipboard(str, useClipboard);
+ if (clipBoard.supportsSelectionClipboard() &&
+ (useClipboard === Ci.nsIClipboard.kSelectionClipboard || clipBoardType === undefined)) {
+ clipBoardHlp.copyStringToClipboard(str, Ci.nsIClipboard.kSelectionClipboard);
+ }
+ }
+ catch (ex) {
+ return false;
+ }
+ return true;
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/commandLine.jsm
@@ -0,0 +1,47 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailCommandLine"];
+
+const EnigmailCompat = ChromeUtils.import("chrome://openpgp/content/modules/compat.jsm").EnigmailCompat;
+
+const NS_ENIGCLINE_SERVICE_CID = Components.ID("{847b3ab1-7ab1-11d4-8f02-006008948af5}");
+const NS_CLINE_SERVICE_CONTRACTID = "@mozilla.org/enigmail/cline-handler;1";
+
+function Handler() {}
+
+Handler.prototype = {
+ classDescription: "Enigmail Key Management CommandLine Service",
+ classID: NS_ENIGCLINE_SERVICE_CID,
+ contractID: NS_CLINE_SERVICE_CONTRACTID,
+ QueryInterface: EnigmailCompat.generateQI(["nsICommandLineHandler", "nsIFactory"]),
+
+ // nsICommandLineHandler
+ handle: function(cmdLine) {
+ if (cmdLine.handleFlag("pgpkeyman", false)) {
+ cmdLine.preventDefault = true; // do not open main app window
+
+ const wwatch = Cc["@mozilla.org/embedcomp/window-watcher;1"].getService(Ci.nsIWindowWatcher);
+ wwatch.openWindow(null, "chrome://openpgp/content/ui/enigmailKeyManager.xul", "_blank", "chrome,dialog=no,all", cmdLine);
+ }
+ },
+
+ helpInfo: " -pgpkeyman Open the OpenPGP key management.\n",
+
+ lockFactory: function(lock) {}
+};
+
+var EnigmailCommandLine = {
+ Handler: Handler,
+ categoryRegistry: {
+ category: "command-line-handler",
+ entry: "m-cline-enigmail",
+ serviceName: NS_CLINE_SERVICE_CONTRACTID
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/compat.jsm
@@ -0,0 +1,182 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * compatibility Module
+ */
+
+var EXPORTED_SYMBOLS = ["EnigmailCompat"];
+
+const XPCOM_APPINFO = "@mozilla.org/xre/app-info;1";
+
+var gTb68OrNewer = null;
+var MailUtils;
+
+MailUtils = ChromeUtils.import("resource:///modules/MailUtils.jsm").MailUtils;
+
+var gCompFields, gPgpMimeObj;
+
+var EnigmailCompat = {
+ generateQI: function(aCid) {
+ if (this.isAtLeastTb68()) {
+ // TB > 60
+ return ChromeUtils.generateQI(aCid);
+ }
+ else {
+ let XPCOMUtils = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm").XPCOMUtils;
+ return XPCOMUtils.generateQI(aCid);
+ }
+ },
+
+ getSecurityField: function() {
+ if (!gCompFields) {
+ gCompFields = Cc["@mozilla.org/messengercompose/composefields;1"].createInstance(Ci.nsIMsgCompFields);
+ }
+ return ("securityInfo" in gCompFields ? /* TB < 64 */ "securityInfo" : "composeSecure");
+ },
+
+ getExistingFolder: function(folderUri) {
+ if ("getExistingFolder" in MailUtils) {
+ // TB >= 65
+ return MailUtils.getExistingFolder(folderUri);
+ }
+ else {
+ return MailUtils.getFolderForURI(folderUri, false);
+ }
+ },
+
+ isMessageUriInPgpMime: function() {
+ if (!gPgpMimeObj) {
+ gPgpMimeObj = Cc["@mozilla.org/mime/pgp-mime-js-decrypt;1"].createInstance(Ci.nsIPgpMimeProxy);
+ }
+
+ return ("messageURI" in gPgpMimeObj);
+ },
+
+ /**
+ * return true, if platform is newer than or equal a given version
+ */
+ isPlatformNewerThan: function(requestedVersion) {
+ let vc = Cc["@mozilla.org/xpcom/version-comparator;1"].getService(Ci.nsIVersionComparator);
+ let appVer = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo).platformVersion;
+
+ return vc.compare(appVer, requestedVersion) >= 0;
+ },
+
+ /**
+ * Get a mail URL from a uriSpec
+ *
+ * @param uriSpec: String - URI of the desired message
+ *
+ * @return Object: nsIURL or nsIMsgMailNewsUrl object
+ */
+ getUrlFromUriSpec: function(uriSpec) {
+ try {
+ if (!uriSpec)
+ return null;
+
+ let messenger = Cc["@mozilla.org/messenger;1"].getService(Ci.nsIMessenger);
+ let msgService = messenger.messageServiceFromURI(uriSpec);
+
+ let url;
+ // TB
+ let urlObj = {};
+ msgService.GetUrlForUri(uriSpec, urlObj, null);
+
+ url = urlObj.value;
+
+ if (url.scheme == "file") {
+ return url;
+ }
+ else {
+ return url.QueryInterface(Ci.nsIMsgMailNewsUrl);
+ }
+
+ }
+ catch (ex) {
+ return null;
+ }
+ },
+ /**
+ * Copy a file to a mail folder.
+ * in nsIFile aFile,
+ * in nsIMsgFolder dstFolder,
+ * in unsigned long aMsgFlags,
+ * in ACString aMsgKeywords,
+ * in nsIMsgCopyServiceListener listener,
+ * in nsIMsgWindow msgWindow
+ */
+ copyFileToMailFolder: function(file, destFolder, msgFlags, msgKeywords, listener, msgWindow) {
+ let copySvc = Cc["@mozilla.org/messenger/messagecopyservice;1"].getService(Ci.nsIMsgCopyService);
+
+ return copySvc.CopyFileMessage(file, destFolder, null, false, msgFlags, msgKeywords, listener, msgWindow);
+ },
+
+ /**
+ * Determine if Platform is at version 68 or newer
+ *
+ * @return {Boolean}: true if at TB 68.0a1 or newer found
+ */
+ isAtLeastTb68: function() {
+ if (gTb68OrNewer === null) {
+ gTb68OrNewer = this.isPlatformNewerThan("68.0a1");
+ }
+
+ return gTb68OrNewer;
+ },
+
+ /**
+ * Get functions that wrap the changes on nsITreeView between TB 60 and TB 68
+ *
+ * @param treeObj
+ * @param listViewHolder
+ *
+ * @return {Object}
+ */
+ getTreeCompatibleFuncs: function(treeObj, listViewHolder) {
+
+ if (this.isAtLeastTb68()) {
+ return {
+ getCellAt: function(x,y) {
+ return treeObj.getCellAt(x, y);
+ },
+ rowCountChanged: function(a, b) {
+ return treeObj.rowCountChanged(a, b);
+ },
+ invalidate: function() {
+ return treeObj.invalidate();
+ },
+ invalidateRow: function(r) {
+ return treeObj.invalidateRow(r);
+ }
+ };
+ }
+ else {
+ return {
+ getCellAt: function(x, y) {
+ let row = {};
+ let col = {};
+ let elt = {};
+ treeObj.treeBoxObject.getCellAt(x, y, row, col, elt);
+
+ return {
+ row: row.value,
+ col: col.value
+ };
+ },
+ rowCountChanged: function(a, b) {
+ return listViewHolder.treebox.rowCountChanged(a, b);
+ },
+ invalidate: function() {
+ return listViewHolder.treebox.invalidate();
+ },
+ invalidateRow: function(r) {
+ return listViewHolder.treebox.invalidateRow(r);
+ }
+ };
+ }
+ },
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/configBackup.jsm
@@ -0,0 +1,213 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailConfigBackup"];
+
+
+
+
+
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailRules = ChromeUtils.import("chrome://openpgp/content/modules/rules.jsm").EnigmailRules;
+const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
+const EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs.jsm").EnigmailPrefs;
+
+const TYPE_BOOL = 1;
+const TYPE_CHAR = 2;
+const TYPE_INT = 3;
+
+const IdentityPref = {
+ enablePgp: TYPE_BOOL,
+ pgpkeyId: TYPE_CHAR,
+ pgpKeyMode: TYPE_INT,
+ pgpSignPlain: TYPE_BOOL,
+ pgpSignEncrypted: TYPE_BOOL,
+ defaultSigningPolicy: TYPE_INT,
+ defaultEncryptionPolicy: TYPE_INT,
+ openPgpUrlName: TYPE_CHAR,
+ pgpMimeMode: TYPE_BOOL,
+ attachPgpKey: TYPE_BOOL,
+ autoEncryptDrafts: TYPE_BOOL
+};
+
+var EnigmailConfigBackup = {
+
+ getAccountManager: function() {
+ let amService = Cc["@mozilla.org/messenger/account-manager;1"].getService(Ci.nsIMsgAccountManager);
+ return amService;
+ },
+
+ /**
+ * itereate over all identities and execute a callback function for each found element
+ *
+ * @param callbackFunc function - the callback for each identity
+ * The function takes the identity as 1st argument, i.e.
+ * callbackFunc(nsIMsgIdentity)
+ * @return - undefined
+ */
+ forAllIdentitites: function(callbackFunc) {
+
+ let amService = this.getAccountManager();
+
+ amService.LoadAccounts(); // ensure accounts are really loaded
+ let a = amService.allIdentities;
+ for (let i = 0; i < a.length; i++) {
+ let id = a.queryElementAt(i, Ci.nsIMsgIdentity);
+ try {
+ callbackFunc(id);
+ }
+ catch (ex) {
+ EnigmailLog.DEBUG("configBackup.jsm: forAllIdentitites: exception " + ex.toString() + "\n");
+ }
+ }
+ },
+
+ /**
+ * backup Enigmail preferences to a file
+ *
+ * @param outputFile nsIFile - handle to file to be saved
+ *
+ * @return 0: success, other values: failure
+ */
+ backupPrefs: function(outputFile) {
+ EnigmailLog.DEBUG("configBackup.jsm: backupPrefs\n");
+
+ // user preference
+ let prefObj = {
+ enigmailPrefs: EnigmailPrefs.getAllPrefs(),
+ mailIdentities: {}
+ };
+
+ function getIdentityPrefs(identity) {
+
+ if (!identity.getBoolAttribute("enablePgp")) return; // do nothing if Enigmail disabled
+
+ let keyObj = {
+ emailAddress: identity.email.toLowerCase(),
+ identityName: identity.identityName
+ };
+
+ for (let pref in IdentityPref) {
+ switch (IdentityPref[pref]) {
+ case TYPE_BOOL:
+ keyObj[pref] = identity.getBoolAttribute(pref);
+ break;
+ case TYPE_INT:
+ keyObj[pref] = identity.getIntAttribute(pref);
+ break;
+ case TYPE_CHAR:
+ keyObj[pref] = identity.getCharAttribute(pref);
+ break;
+ }
+ }
+
+ prefObj.mailIdentities[identity.key] = keyObj;
+ }
+
+ this.forAllIdentitites(getIdentityPrefs);
+
+ // per-recipient rules (aka pgpRules.xml)
+ var rulesFile = EnigmailRules.getRulesFile();
+ if (rulesFile.exists()) {
+ prefObj.rules = EnigmailFiles.readFile(rulesFile);
+ }
+
+ let jsonStr = JSON.stringify(prefObj);
+
+ // serialize everything to UTF-8 encoded JSON.
+ if (EnigmailFiles.writeFileContents(outputFile, jsonStr)) {
+ return 0;
+ }
+
+ return -1;
+ },
+
+ /**
+ * Restore Enigmail preferences from a file as generated by backpPrefs()
+ *
+ * @param inputFile nsIFile - handle to file to be saved
+ *
+ * @return Object: {
+ * retVal: Number - 0: success, other values: failure
+ * unmatchedIds: Array (String): keys of identities
+ * }
+ */
+ restorePrefs: function(inputFile) {
+ EnigmailLog.DEBUG("configBackup.jsm: restorePrefs\n");
+ var prefObj;
+ var returnObj = {
+ retVal: -1,
+ unmatchedIds: []
+ };
+
+ function setIdentityPref(identity) {
+ for (let k in prefObj.mailIdentities) {
+ if (prefObj.mailIdentities[k].emailAddress === identity.email.toLowerCase()) {
+ EnigmailLog.DEBUG("configBackup.jsm: setIdentityPref: restoring values for " + identity.email + "\n");
+ prefObj.mailIdentities[k].foundMatchingEmail = true;
+ let keyObj = prefObj.mailIdentities[k];
+ for (let pref in IdentityPref) {
+ switch (IdentityPref[pref]) {
+ case TYPE_BOOL:
+ identity.setBoolAttribute(pref, keyObj[pref]);
+ break;
+ case TYPE_INT:
+ identity.setIntAttribute(pref, keyObj[pref]);
+ break;
+ case TYPE_CHAR:
+ identity.setCharAttribute(pref, keyObj[pref]);
+ break;
+ }
+ }
+ return;
+ }
+ }
+
+ EnigmailLog.DEBUG("configBackup.jsm: setIdentityPref: no matching data for " + identity.email + "\n");
+ }
+
+ // Profile must be a single UTF-8 encoded JSON object.
+ try {
+ let jsonStr = EnigmailFiles.readFile(inputFile);
+ prefObj = JSON.parse(jsonStr);
+
+ var nsIPB = Ci.nsIPrefBranch;
+ var branch = EnigmailPrefs.getPrefBranch();
+
+ // Set all options recorded in the JSON file.
+ for (let name in prefObj.enigmailPrefs) {
+ EnigmailPrefs.setPref(name, prefObj.enigmailPrefs[name]);
+ }
+
+ this.forAllIdentitites(setIdentityPref);
+
+ for (let i in prefObj.mailIdentities) {
+ if (!("foundMatchingEmail" in prefObj.mailIdentities[i])) {
+ returnObj.unmatchedIds.push(prefObj.mailIdentities[i].identityName);
+ }
+ }
+
+ let am = this.getAccountManager();
+ am.saveAccountInfo();
+ EnigmailPrefs.savePrefs();
+
+ if ("rules" in prefObj) {
+ EnigmailRules.loadRulesFromString(prefObj.rules);
+ EnigmailRules.saveRulesFile();
+ }
+
+ }
+ catch (ex) {
+ EnigmailLog.ERROR("configBackup.jsm: restorePrefs - exception " + ex.toString() + "\n");
+ return returnObj;
+ }
+
+ returnObj.retVal = 0;
+ return returnObj;
+ }
+
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/configure.jsm
@@ -0,0 +1,301 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailConfigure"];
+
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs.jsm").EnigmailPrefs;
+const EnigmailTimer = ChromeUtils.import("chrome://openpgp/content/modules/timer.jsm").EnigmailTimer;
+const EnigmailApp = ChromeUtils.import("chrome://openpgp/content/modules/app.jsm").EnigmailApp;
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+const EnigmailDialog = ChromeUtils.import("chrome://openpgp/content/modules/dialog.jsm").EnigmailDialog;
+const EnigmailWindows = ChromeUtils.import("chrome://openpgp/content/modules/windows.jsm").EnigmailWindows;
+const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
+const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
+const EnigmailStdlib = ChromeUtils.import("chrome://openpgp/content/modules/stdlib.jsm").EnigmailStdlib;
+const EnigmailLazy = ChromeUtils.import("chrome://openpgp/content/modules/lazy.jsm").EnigmailLazy;
+const EnigmailAutoSetup = ChromeUtils.import("chrome://openpgp/content/modules/autoSetup.jsm").EnigmailAutoSetup;
+const EnigmailSqliteDb = ChromeUtils.import("chrome://openpgp/content/modules/sqliteDb.jsm").EnigmailSqliteDb;
+
+// Interfaces
+const nsIFolderLookupService = Ci.nsIFolderLookupService;
+const nsIMsgAccountManager = Ci.nsIMsgAccountManager;
+
+/**
+ * Upgrade sending prefs
+ * (v1.6.x -> v1.7 )
+ */
+function upgradePrefsSending() {
+ EnigmailLog.DEBUG("enigmailCommon.jsm: upgradePrefsSending()\n");
+
+ var cbs = EnigmailPrefs.getPref("confirmBeforeSend");
+ var ats = EnigmailPrefs.getPref("alwaysTrustSend");
+ var ksfr = EnigmailPrefs.getPref("keepSettingsForReply");
+ EnigmailLog.DEBUG("enigmailCommon.jsm: upgradePrefsSending cbs=" + cbs + " ats=" + ats + " ksfr=" + ksfr + "\n");
+
+ // Upgrade confirmBeforeSend (bool) to confirmBeforeSending (int)
+ switch (cbs) {
+ case false:
+ EnigmailPrefs.setPref("confirmBeforeSending", 0); // never
+ break;
+ case true:
+ EnigmailPrefs.setPref("confirmBeforeSending", 1); // always
+ break;
+ }
+
+ // Upgrade alwaysTrustSend (bool) to acceptedKeys (int)
+ switch (ats) {
+ case false:
+ EnigmailPrefs.setPref("acceptedKeys", 0); // valid
+ break;
+ case true:
+ EnigmailPrefs.setPref("acceptedKeys", 1); // all
+ break;
+ }
+
+ // if all settings are default settings, use convenient encryption
+ if (cbs === false && ats === true && ksfr === true) {
+ EnigmailPrefs.setPref("encryptionModel", 0); // convenient
+ EnigmailLog.DEBUG("enigmailCommon.jsm: upgradePrefsSending() encryptionModel=0 (convenient)\n");
+ }
+ else {
+ EnigmailPrefs.setPref("encryptionModel", 1); // manually
+ EnigmailLog.DEBUG("enigmailCommon.jsm: upgradePrefsSending() encryptionModel=1 (manually)\n");
+ }
+
+ // clear old prefs
+ EnigmailPrefs.getPrefBranch().clearUserPref("confirmBeforeSend");
+ EnigmailPrefs.getPrefBranch().clearUserPref("alwaysTrustSend");
+}
+
+/**
+ * Replace short key IDs with FPR in identity settings
+ * (v1.9 -> v2.0)
+ */
+function replaceKeyIdWithFpr() {
+ try {
+ const GetKeyRing = EnigmailLazy.loader("enigmail/keyRing.jsm", "EnigmailKeyRing");
+
+ var accountManager = Cc["@mozilla.org/messenger/account-manager;1"].getService(Ci.nsIMsgAccountManager);
+ for (var i = 0; i < accountManager.allIdentities.length; i++) {
+ var id = accountManager.allIdentities.queryElementAt(i, Ci.nsIMsgIdentity);
+ if (id.getBoolAttribute("enablePgp")) {
+ let keyId = id.getCharAttribute("pgpkeyId");
+
+ if (keyId.search(/^(0x)?[a-fA-F0-9]{8}$/) === 0) {
+
+ EnigmailCore.getService();
+
+ let k = GetKeyRing().getKeyById(keyId);
+ if (k) {
+ id.setCharAttribute("pgpkeyId", "0x" + k.fpr);
+ }
+ else {
+ id.setCharAttribute("pgpkeyId", "");
+ }
+ }
+ }
+ }
+ }
+ catch (ex) {
+ EnigmailDialog.alert("config upgrade: error" + ex.toString());
+ }
+}
+
+
+/**
+ * Change the default to PGP/MIME for all accounts, except nntp
+ * (v1.8.x -> v1.9)
+ */
+function defaultPgpMime() {
+ let accountManager = Cc["@mozilla.org/messenger/account-manager;1"].getService(Ci.nsIMsgAccountManager);
+ let changedSomething = false;
+
+ for (let acct = 0; acct < accountManager.accounts.length; acct++) {
+ let ac = accountManager.accounts.queryElementAt(acct, Ci.nsIMsgAccount);
+ if (ac.incomingServer.type.search(/(pop3|imap|movemail)/) >= 0) {
+
+ for (let i = 0; i < ac.identities.length; i++) {
+ let id = ac.identities.queryElementAt(i, Ci.nsIMsgIdentity);
+ if (id.getBoolAttribute("enablePgp") && !id.getBoolAttribute("pgpMimeMode")) {
+ changedSomething = true;
+ }
+ id.setBoolAttribute("pgpMimeMode", true);
+ }
+ }
+ }
+
+ if (EnigmailPrefs.getPref("advancedUser") && changedSomething) {
+ EnigmailDialog.alert(null,
+ EnigmailLocale.getString("preferences.defaultToPgpMime"));
+ }
+}
+
+/**
+ * set the Autocrypt prefer-encrypt option to "mutual" for all existing
+ * accounts
+ */
+function setAutocryptForOldAccounts() {
+ try {
+ let accountManager = Cc["@mozilla.org/messenger/account-manager;1"].getService(Ci.nsIMsgAccountManager);
+ let changedSomething = false;
+
+ for (let acct = 0; acct < accountManager.accounts.length; acct++) {
+ let ac = accountManager.accounts.queryElementAt(acct, Ci.nsIMsgAccount);
+ if (ac.incomingServer.type.search(/(pop3|imap|movemail)/) >= 0) {
+ ac.incomingServer.setIntValue("acPreferEncrypt", 1);
+ }
+ }
+ }
+ catch (ex) {}
+}
+
+function setDefaultKeyServer() {
+ EnigmailLog.DEBUG("configure.jsm: setDefaultKeyServer()\n");
+
+ let ks = EnigmailPrefs.getPref("keyserver");
+
+ if (ks.search(/^ldaps?:\/\//) < 0) {
+ ks = "vks://keys.openpgp.org, " + ks;
+ }
+
+ ks = ks.replace(/hkps:\/\/keys.openpgp.org/g, "vks://keys.openpgp.org");
+ EnigmailPrefs.setPref("keyserver", ks);
+}
+
+
+
+function displayUpgradeInfo() {
+ EnigmailLog.DEBUG("configure.jsm: displayUpgradeInfo()\n");
+ try {
+ EnigmailWindows.openMailTab("chrome://openpgp/content/ui/upgradeInfo.html");
+ }
+ catch (ex) {}
+}
+
+
+var EnigmailConfigure = {
+ /**
+ * configureEnigmail: main function for configuring Enigmail during the first run
+ * this method is called from core.jsm if Enigmail has not been set up before
+ * (determined via checking the configuredVersion in the preferences)
+ *
+ * @param {nsIWindow} win: The parent window. Null if no parent window available
+ * @param {Boolean} startingPreferences: if true, called while switching to new preferences
+ * (to avoid re-check for preferences)
+ *
+ * @return {Promise<null>}
+ */
+ configureEnigmail: async function(win, startingPreferences) {
+ EnigmailLog.DEBUG("configure.jsm: configureEnigmail()\n");
+
+ if (!EnigmailStdlib.hasConfiguredAccounts()) {
+ EnigmailLog.DEBUG("configure.jsm: configureEnigmail: no account configured. Waiting 60 seconds.\n");
+
+ // try again in 60 seconds
+ EnigmailTimer.setTimeout(
+ function _f() {
+ EnigmailConfigure.configureEnigmail(win, startingPreferences);
+ },
+ 60000);
+ return;
+ }
+
+ let oldVer = EnigmailPrefs.getPref("configuredVersion");
+
+ let vc = Cc["@mozilla.org/xpcom/version-comparator;1"].getService(Ci.nsIVersionComparator);
+
+ if (oldVer === "") {
+ try {
+ let setupResult = await EnigmailAutoSetup.determinePreviousInstallType();
+
+ switch (EnigmailAutoSetup.value) {
+ case EnigmailConstants.AUTOSETUP_NOT_INITIALIZED:
+ case EnigmailConstants.AUTOSETUP_NO_ACCOUNT:
+ break;
+ default:
+ EnigmailPrefs.setPref("configuredVersion", EnigmailApp.getVersion());
+ EnigmailWindows.openSetupWizard(win);
+ }
+ }
+ catch(x) {
+ // ignore exceptions and proceed without setup wizard
+ }
+ }
+ else {
+ if (vc.compare(oldVer, "1.7a1pre") < 0) {
+ // 1: rules only
+ // => assignKeysByRules true; rest false
+ // 2: rules & email addresses (normal)
+ // => assignKeysByRules/assignKeysByEmailAddr/assignKeysManuallyIfMissing true
+ // 3: email address only (no rules)
+ // => assignKeysByEmailAddr/assignKeysManuallyIfMissing true
+ // 4: manually (always prompt, no rules)
+ // => assignKeysManuallyAlways true
+ // 5: no rules, no key selection
+ // => assignKeysByRules/assignKeysByEmailAddr true
+
+ upgradePrefsSending();
+ }
+ if (vc.compare(oldVer, "1.7") < 0) {
+ // open a modal dialog. Since this might happen during the opening of another
+ // window, we have to do this asynchronously
+ EnigmailTimer.setTimeout(
+ function _cb() {
+ var doIt = EnigmailDialog.confirmDlg(win,
+ EnigmailLocale.getString("enigmailCommon.versionSignificantlyChanged"),
+ EnigmailLocale.getString("enigmailCommon.checkPreferences"),
+ EnigmailLocale.getString("dlg.button.close"));
+ if (!startingPreferences && doIt) {
+ // same as:
+ // - EnigmailWindows.openPrefWindow(window, true, 'sendingTab');
+ // but
+ // - without starting the service again because we do that right now
+ // - and modal (waiting for its end)
+ win.openDialog("chrome://openpgp/content/ui/pref-enigmail.xul",
+ "_blank", "chrome,resizable=yes,modal", {
+ 'showBasic': true,
+ 'clientType': 'thunderbird',
+ 'selectTab': 'sendingTab'
+ });
+ }
+ }, 100);
+ }
+
+ if (vc.compare(oldVer, "1.9a2pre") < 0) {
+ defaultPgpMime();
+ }
+ if (vc.compare(oldVer, "2.0a1pre") < 0) {
+ this.upgradeTo20();
+ }
+ if (vc.compare(oldVer, "2.0.1a2pre") < 0) {
+ this.upgradeTo201();
+ }
+ if (vc.compare(oldVer, "2.1b2") < 0) {
+ this.upgradeTo21();
+ }
+
+ }
+
+ EnigmailPrefs.setPref("configuredVersion", EnigmailApp.getVersion());
+ EnigmailPrefs.savePrefs();
+ },
+
+ upgradeTo20: function() {
+ replaceKeyIdWithFpr();
+ displayUpgradeInfo();
+ },
+
+ upgradeTo201: function() {
+ setAutocryptForOldAccounts();
+ },
+
+ upgradeTo21: function() {
+ setDefaultKeyServer();
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/constants.jsm
@@ -0,0 +1,166 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailConstants"];
+
+var EnigmailConstants = {
+ POSSIBLE_PGPMIME: -2081,
+
+ // possible values for
+ // - encryptByRule, signByRules, pgpmimeByRules
+ // - encryptForced, signForced, pgpmimeForced (except CONFLICT)
+ // NOTE:
+ // - values 0/1/2 are used with this fixed semantics in the persistent rules
+ // - see also enigmailEncryptionDlg.xul
+ ENIG_NEVER: 0,
+ ENIG_UNDEF: 1,
+ ENIG_ALWAYS: 2,
+ ENIG_FORCE_SMIME: 3,
+ ENIG_AUTO_ALWAYS: 22,
+ ENIG_CONFLICT: 99,
+
+ ENIG_FINAL_UNDEF: -1,
+ ENIG_FINAL_NO: 0,
+ ENIG_FINAL_YES: 1,
+ ENIG_FINAL_FORCENO: 10,
+ ENIG_FINAL_FORCEYES: 11,
+ ENIG_FINAL_SMIME: 97, // use S/MIME (automatically chosen)
+ ENIG_FINAL_FORCESMIME: 98, // use S/MIME (forced by user)
+ ENIG_FINAL_CONFLICT: 99,
+
+ MIME_HANDLER_UNDEF: 0,
+ MIME_HANDLER_SMIME: 1,
+ MIME_HANDLER_PGPMIME: 2,
+
+ ICONTYPE_INFO: 1,
+ ICONTYPE_QUESTION: 2,
+ ICONTYPE_ALERT: 3,
+ ICONTYPE_ERROR: 4,
+
+ FILTER_MOVE_DECRYPT: "enigmail@enigmail.net#filterActionMoveDecrypt",
+ FILTER_COPY_DECRYPT: "enigmail@enigmail.net#filterActionCopyDecrypt",
+ FILTER_ENCRYPT: "enigmail@enigmail.net#filterActionEncrypt",
+ FILTER_TERM_PGP_ENCRYPTED: "enigmail@enigmail.net#filterTermPGPEncrypted",
+
+ /* taken over from old nsIEnigmail */
+
+ /* Cleartext signature parts */
+ SIGNATURE_TEXT: 1,
+ SIGNATURE_HEADERS: 2,
+ SIGNATURE_ARMOR: 3,
+
+ /* User interaction flags */
+ UI_INTERACTIVE: 0x01,
+ UI_ALLOW_KEY_IMPORT: 0x02,
+ UI_UNVERIFIED_ENC_OK: 0x04,
+ UI_PGP_MIME: 0x08,
+ UI_TEST: 0x10,
+ UI_RESTORE_STRICTLY_MIME: 0x20,
+ UI_IGNORE_MDC_ERROR: 0x40, // force decryption, even if we got an MDC error
+
+ /* Send message flags */
+ SEND_SIGNED: 0x0001, // 1
+ SEND_ENCRYPTED: 0x0002, // 2
+ SEND_DEFAULT: 0x0004, // 4
+ SEND_LATER: 0x0008, // 8
+ SEND_WITH_CHECK: 0x0010, // 16
+ SEND_ALWAYS_TRUST: 0x0020, // 32
+ SEND_ENCRYPT_TO_SELF: 0x0040, // 64
+ SEND_PGP_MIME: 0x0080, // 128
+ SEND_TEST: 0x0100, // 256
+ SAVE_MESSAGE: 0x0200, // 512
+ SEND_STRIP_WHITESPACE: 0x0400, // 1024
+ SEND_ATTACHMENT: 0x0800, // 2048
+ ENCRYPT_HEADERS: 0x1000, // 4096
+ SEND_VERBATIM: 0x2000, // 8192
+
+ /* Status flags */
+ GOOD_SIGNATURE: 0x00000001,
+ BAD_SIGNATURE: 0x00000002,
+ UNVERIFIED_SIGNATURE: 0x00000004,
+ EXPIRED_SIGNATURE: 0x00000008,
+ EXPIRED_KEY_SIGNATURE: 0x00000010,
+ EXPIRED_KEY: 0x00000020,
+ REVOKED_KEY: 0x00000040,
+ NO_PUBKEY: 0x00000080,
+ NO_SECKEY: 0x00000100,
+ IMPORTED_KEY: 0x00000200,
+ INVALID_RECIPIENT: 0x00000400,
+ MISSING_PASSPHRASE: 0x00000800,
+ BAD_PASSPHRASE: 0x00001000,
+ BAD_ARMOR: 0x00002000,
+ NODATA: 0x00004000,
+ DECRYPTION_INCOMPLETE: 0x00008000,
+ DECRYPTION_FAILED: 0x00010000,
+ DECRYPTION_OKAY: 0x00020000,
+ MISSING_MDC: 0x00040000,
+ TRUSTED_IDENTITY: 0x00080000,
+ PGP_MIME_SIGNED: 0x00100000,
+ PGP_MIME_ENCRYPTED: 0x00200000,
+ DISPLAY_MESSAGE: 0x00400000,
+ INLINE_KEY: 0x00800000,
+ PARTIALLY_PGP: 0x01000000,
+ PHOTO_AVAILABLE: 0x02000000,
+ OVERFLOWED: 0x04000000,
+ CARDCTRL: 0x08000000,
+ SC_OP_FAILURE: 0x10000000,
+ UNKNOWN_ALGO: 0x20000000,
+ SIG_CREATED: 0x40000000,
+ END_ENCRYPTION: 0x80000000,
+
+ /*** key handling functions ***/
+
+ EXTRACT_SECRET_KEY: 0x01,
+
+ /* Keyserver Action Flags */
+ SEARCH_KEY: 1,
+ DOWNLOAD_KEY: 2,
+ UPLOAD_KEY: 3,
+ REFRESH_KEY: 4,
+ GET_SKS_CACERT: 5,
+ UPLOAD_WKD: 6,
+ GET_CONFIRMATION_LINK: 7,
+
+ /* attachment handling */
+
+ /* per-recipient rules */
+ AC_RULE_PREFIX: "autocrypt://",
+
+ CARD_PIN_CHANGE: 1,
+ CARD_PIN_UNBLOCK: 2,
+ CARD_ADMIN_PIN_CHANGE: 3,
+
+ /* Keyserver error codes (in keyserver.jsm) */
+ KEYSERVER_ERR_ABORTED: 1,
+ KEYSERVER_ERR_SERVER_ERROR: 2,
+ KEYSERVER_ERR_SECURITY_ERROR: 3,
+ KEYSERVER_ERR_CERTIFICATE_ERROR: 4,
+ KEYSERVER_ERR_SERVER_UNAVAILABLE: 5,
+ KEYSERVER_ERR_IMPORT_ERROR: 6,
+ KEYSERVER_ERR_UNKNOWN: 7,
+
+ /* AutocryptSeup Setup Type */
+ AUTOSETUP_NOT_INITIALIZED: 0,
+ AUTOSETUP_AC_SETUP_MSG: 1,
+ AUTOSETUP_AC_HEADER: 2,
+ AUTOSETUP_PEP_HEADER: 3,
+ AUTOSETUP_ENCRYPTED_MSG: 4,
+ AUTOSETUP_NO_HEADER: 5,
+ AUTOSETUP_NO_ACCOUNT: 6,
+
+ /* Bootstrapped Addon constants */
+ APP_STARTUP: 1, // The application is starting up.
+ APP_SHUTDOWN: 2, // The application is shutting down.
+ ADDON_ENABLE: 3, // The add-on is being enabled.
+ ADDON_DISABLE: 4, // The add-on is being disabled. (Also sent during uninstallation)
+ ADDON_INSTALL: 5, // The add-on is being installed.
+ ADDON_UNINSTALL: 6, // The add-on is being uninstalled.
+ ADDON_UPGRADE: 7, // The add-on is being upgraded.
+ ADDON_DOWNGRADE: 8 // The add-on is being downgraded.
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/core.jsm
@@ -0,0 +1,478 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+const {
+ manager: Cm,
+ Constructor: CC
+} = Components;
+Cm.QueryInterface(Ci.nsIComponentRegistrar);
+
+const subprocess = ChromeUtils.import("chrome://openpgp/content/modules/subprocess.jsm").subprocess;
+const EnigmailLazy = ChromeUtils.import("chrome://openpgp/content/modules/lazy.jsm").EnigmailLazy;
+const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
+
+// load all modules lazily to avoid possible cross-reference errors
+const getEnigmailConsole = EnigmailLazy.loader("enigmail/pipeConsole.jsm", "EnigmailConsole");
+const getEnigmailGpgAgent = EnigmailLazy.loader("enigmail/gpgAgent.jsm", "EnigmailGpgAgent");
+const getEnigmailMimeEncrypt = EnigmailLazy.loader("enigmail/mimeEncrypt.jsm", "EnigmailMimeEncrypt");
+const getEnigmailProtocolHandler = EnigmailLazy.loader("enigmail/protocolHandler.jsm", "EnigmailProtocolHandler");
+const getEnigmailFiltersWrapper = EnigmailLazy.loader("enigmail/filtersWrapper.jsm", "EnigmailFiltersWrapper");
+const getEnigmailLog = EnigmailLazy.loader("enigmail/log.jsm", "EnigmailLog");
+const getEnigmailOS = EnigmailLazy.loader("enigmail/os.jsm", "EnigmailOS");
+const getEnigmailLocale = EnigmailLazy.loader("enigmail/locale.jsm", "EnigmailLocale");
+const getEnigmailCommandLine = EnigmailLazy.loader("enigmail/commandLine.jsm", "EnigmailCommandLine");
+const getEnigmailPrefs = EnigmailLazy.loader("enigmail/prefs.jsm", "EnigmailPrefs");
+const getEnigmailVerify = EnigmailLazy.loader("enigmail/mimeVerify.jsm", "EnigmailVerify");
+const getEnigmailWindows = EnigmailLazy.loader("enigmail/windows.jsm", "EnigmailWindows");
+const getEnigmailDialog = EnigmailLazy.loader("enigmail/dialog.jsm", "EnigmailDialog");
+const getEnigmailConfigure = EnigmailLazy.loader("enigmail/configure.jsm", "EnigmailConfigure");
+const getEnigmailApp = EnigmailLazy.loader("enigmail/app.jsm", "EnigmailApp");
+const getEnigmailKeyRefreshService = EnigmailLazy.loader("enigmail/keyRefreshService.jsm", "EnigmailKeyRefreshService");
+const getEnigmailKeyServer = EnigmailLazy.loader("enigmail/keyserver.jsm", "EnigmailKeyServer");
+const getEnigmailWksMimeHandler = EnigmailLazy.loader("enigmail/wksMimeHandler.jsm", "EnigmailWksMimeHandler");
+const getEnigmailOverlays = EnigmailLazy.loader("enigmail/enigmailOverlays.jsm", "EnigmailOverlays");
+const getEnigmailSqlite = EnigmailLazy.loader("enigmail/sqliteDb.jsm", "EnigmailSqliteDb");
+const getEnigmailTimer = EnigmailLazy.loader("enigmail/timer.jsm", "EnigmailTimer");
+const Services = ChromeUtils.import("resource://gre/modules/Services.jsm").Services;
+
+var EXPORTED_SYMBOLS = ["EnigmailCore"];
+
+// Interfaces
+const nsIEnvironment = Ci.nsIEnvironment;
+
+var gPreferredGpgPath = null;
+var gOverwriteEnvVar = [];
+var gEnigmailService = null; // Global Enigmail Service
+
+var gEnvList = null; // currently filled from enigmail.js
+
+var EnigmailCore = {
+ /**
+ * Create a new instance of Enigmail, or return the already existing one
+ */
+ createInstance: function() {
+ if (!gEnigmailService) {
+ gEnigmailService = new Enigmail();
+ }
+
+ return gEnigmailService;
+ },
+
+ startup: function(reason) {
+ let self = this;
+
+ let env = getEnvironment();
+ initializeLogDirectory();
+ initializeLogging(env);
+
+ const logger = getEnigmailLog();
+
+ logger.DEBUG("core.jsm: startup()\n");
+
+ getEnigmailSqlite().checkDatabaseStructure();
+ getEnigmailPrefs().startup(reason);
+
+ this.factories = [];
+
+ function continueStartup(type) {
+ logger.DEBUG(`core.jsm: startup.continueStartup(${type})\n`);
+
+ try {
+ let mimeEncrypt = getEnigmailMimeEncrypt();
+ mimeEncrypt.startup(reason);
+ getEnigmailOverlays().startup();
+ self.factories.push(new Factory(getEnigmailProtocolHandler()));
+ self.factories.push(new Factory(mimeEncrypt.Handler));
+ } catch (ex) {
+ logger.DEBUG("core.jsm: startup.continueStartup: error " + ex.message + "\n" + ex.stack + "\n");
+ }
+ }
+
+ getEnigmailVerify().registerContentTypeHandler();
+ getEnigmailWksMimeHandler().registerContentTypeHandler();
+ getEnigmailFiltersWrapper().onStartup();
+ continueStartup(1);
+ },
+
+ shutdown: function(reason) {
+ getEnigmailLog().DEBUG("core.jsm: shutdown():\n");
+
+ let cLineReg = getEnigmailCommandLine().categoryRegistry;
+ let catMan = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
+ catMan.deleteCategoryEntry(cLineReg.category, cLineReg.entry, false);
+
+ if (this.factories) {
+ for (let fct of this.factories) {
+ fct.unregister();
+ }
+ }
+
+ getEnigmailFiltersWrapper().onShutdown();
+ getEnigmailVerify().unregisterContentTypeHandler();
+
+ getEnigmailGpgAgent().finalize();
+ getEnigmailLocale().shutdown();
+ getEnigmailLog().onShutdown();
+
+ getEnigmailLog().setLogLevel(3);
+ gEnigmailService = null;
+ },
+
+ version: "",
+
+ init: function(enigmailVersion) {
+ this.version = enigmailVersion;
+ },
+
+ /**
+ * get and or initialize the Enigmail service,
+ * including the handling for upgrading old preferences to new versions
+ *
+ * @win: - nsIWindow: parent window (optional)
+ * @startingPreferences - Boolean: true - called while switching to new preferences
+ * (to avoid re-check for preferences)
+ */
+ getService: function(win, startingPreferences) {
+ // Lazy initialization of Enigmail JS component (for efficiency)
+
+ if (gEnigmailService) {
+ return gEnigmailService.initialized ? gEnigmailService : null;
+ }
+
+ try {
+ this.createInstance();
+ return gEnigmailService.getService(win, startingPreferences);
+ } catch (ex) {
+ return null;
+ }
+
+ },
+
+ getEnigmailService: function() {
+ return gEnigmailService;
+ },
+
+ setEnigmailService: function(v) {
+ gEnigmailService = v;
+ },
+
+ /**
+ * obtain a list of all environment variables
+ *
+ * @return: Array of Strings with the following structrue
+ * variable_name=variable_content
+ */
+ getEnvList: function() {
+ return gEnvList;
+ },
+
+ addToEnvList: function(str) {
+ gEnvList.push(str);
+ },
+
+ setEnvVariable: function(varname, value) {
+ for (let i = 0; i < gEnvList.length; i++) {
+ if (gEnvList[i].startsWith(varname + "=")) {
+ gEnvList[i] = varname + "=" + value;
+ break;
+ }
+ }
+ }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Enigmail encryption/decryption service
+///////////////////////////////////////////////////////////////////////////////
+
+function getLogDirectoryPrefix() {
+ try {
+ return getEnigmailPrefs().getPrefBranch().getCharPref("logDirectory") || "";
+ } catch (ex) {
+ return "";
+ }
+}
+
+function initializeLogDirectory() {
+ const prefix = getLogDirectoryPrefix();
+ if (prefix) {
+ getEnigmailLog().setLogLevel(5);
+ getEnigmailLog().setLogDirectory(prefix);
+ getEnigmailLog().DEBUG("core.jsm: Logging debug output to " + prefix + "/enigdbug.txt\n");
+ }
+}
+
+function initializeLogging(env) {
+ const nspr_log_modules = env.get("NSPR_LOG_MODULES");
+ const matches = nspr_log_modules.match(/enigmail.js:(\d+)/);
+
+ if (matches && (matches.length > 1)) {
+ getEnigmailLog().setLogLevel(Number(matches[1]));
+ getEnigmailLog().WARNING("core.jsm: Enigmail: LogLevel=" + matches[1] + "\n");
+ }
+}
+
+function initializeSubprocessLogging(env) {
+ const nspr_log_modules = env.get("NSPR_LOG_MODULES");
+ const matches = nspr_log_modules.match(/subprocess:(\d+)/);
+
+ subprocess.registerLogHandler(function(txt) {
+ getEnigmailLog().ERROR("subprocess.jsm: " + txt);
+ });
+
+ if (matches && matches.length > 1 && matches[1] > 2) {
+ subprocess.registerDebugHandler(function(txt) {
+ getEnigmailLog().DEBUG("subprocess.jsm: " + txt);
+ });
+ }
+}
+
+function initializeAgentInfo() {
+ if (!getEnigmailOS().isDosLike && !getEnigmailGpgAgent().isDummy()) {
+ EnigmailCore.addToEnvList("GPG_AGENT_INFO=" + getEnigmailGpgAgent().gpgAgentInfo.envStr);
+ }
+}
+
+function failureOn(ex, status) {
+ status.initializationError = getEnigmailLocale().getString("enigmailNotAvailable");
+ getEnigmailLog().ERROR("core.jsm: Enigmail.initialize: Error - " + status.initializationError + "\n");
+ getEnigmailLog().DEBUG("core.jsm: Enigmail.initialize: exception=" + ex.toString() + "\n");
+ throw Components.results.NS_ERROR_FAILURE;
+}
+
+function getEnvironment(status) {
+ try {
+ return Cc["@mozilla.org/process/environment;1"].getService(nsIEnvironment);
+ } catch (ex) {
+ failureOn(ex, status);
+ }
+ return null;
+}
+
+function initializeEnvironment(env) {
+ // Initialize global environment variables list
+ let passEnv = ["GNUPGHOME", "GPGDIR", "ETC",
+ "ALLUSERSPROFILE", "APPDATA", "LOCALAPPDATA", "BEGINLIBPATH",
+ "COMMONPROGRAMFILES", "COMSPEC", "DBUS_SESSION_BUS_ADDRESS", "DISPLAY",
+ "ENIGMAIL_PASS_ENV", "ENDLIBPATH",
+ "GTK_IM_MODULE",
+ "HOME", "HOMEDRIVE", "HOMEPATH",
+ "LOCPATH", "LOGNAME", "LD_LIBRARY_PATH", "MOZILLA_FIVE_HOME",
+ "NLSPATH", "PATH", "PATHEXT", "PINENTRY_USER_DATA", "PROGRAMFILES", "PWD",
+ "QT_IM_MODULE",
+ "SHELL", "SYSTEMDRIVE", "SYSTEMROOT",
+ "TEMP", "TMP", "TMPDIR", "TZ", "TZDIR", "UNIXROOT",
+ "USER", "USERPROFILE", "WINDIR", "XAUTHORITY",
+ "XMODIFIERS"
+ ];
+
+ gEnvList = [];
+
+ // if (!getEnigmailPrefs().getPref("gpgLocaleEn")) {
+ // passEnv = passEnv.concat([
+ // "LANG", "LANGUAGE", "LC_ALL", "LC_COLLATE", "LC_CTYPE",
+ // "LC_MESSAGES", "LC_MONETARY", "LC_NUMERIC", "LC_TIME"
+ // ]);
+ // }
+ // else if (getEnigmailOS().getOS() === "WINNT") {
+ // // force output on Windows to EN-US
+ // EnigmailCore.addToEnvList("LC_ALL=en_US");
+ // EnigmailCore.addToEnvList("LANG=en_US");
+ // }
+
+ EnigmailCore.addToEnvList("LC_ALL=C");
+ EnigmailCore.addToEnvList("LANG=C");
+
+ const passList = env.get("ENIGMAIL_PASS_ENV");
+ if (passList) {
+ const passNames = passList.split(":");
+ for (var k = 0; k < passNames.length; k++) {
+ passEnv.push(passNames[k]);
+ }
+ }
+
+ for (var j = 0; j < passEnv.length; j++) {
+ const envName = passEnv[j];
+ let envValue;
+
+ if (envName in gOverwriteEnvVar) {
+ envValue = gOverwriteEnvVar[envName];
+ } else {
+ envValue = env.get(envName);
+ }
+ if (envValue) {
+ EnigmailCore.addToEnvList(envName + "=" + envValue);
+ }
+ }
+
+ getEnigmailLog().DEBUG("core.jsm: Enigmail.initialize: Ec.envList = " + gEnvList + "\n");
+}
+
+
+function Enigmail() {
+ this.wrappedJSObject = this;
+}
+
+Enigmail.prototype = {
+ initialized: false,
+ initializationAttempted: false,
+ initializationError: "",
+
+ initialize: function(domWindow, version) {
+ this.initializationAttempted = true;
+
+ getEnigmailLog().DEBUG("core.jsm: Enigmail.initialize: START\n");
+
+ if (this.initialized) return;
+
+ this.environment = getEnvironment(this);
+
+ initializeSubprocessLogging(this.environment);
+ initializeEnvironment(this.environment);
+
+ try {
+ getEnigmailConsole().write("Initializing Enigmail service ...\n");
+ } catch (ex) {
+ failureOn(ex, this);
+ }
+
+ getEnigmailGpgAgent().setAgentPath(domWindow, this, gPreferredGpgPath);
+ getEnigmailGpgAgent().detectGpgAgent(domWindow, this);
+
+ initializeAgentInfo();
+
+ getEnigmailKeyRefreshService().start(getEnigmailKeyServer());
+
+ this.initialized = true;
+
+ getEnigmailLog().DEBUG("core.jsm: Enigmail.initialize: END\n");
+ },
+
+ reinitialize: function() {
+ getEnigmailLog().DEBUG("core.jsm: Enigmail.reinitialize:\n");
+ this.initialized = false;
+ this.initializationAttempted = true;
+
+ getEnigmailConsole().write("Reinitializing Enigmail service ...\n");
+ initializeEnvironment(this.environment);
+ getEnigmailGpgAgent().setAgentPath(null, this, gPreferredGpgPath);
+ this.initialized = true;
+ },
+
+ perferGpgPath: function(gpgPath) {
+ getEnigmailLog().DEBUG("core.jsm: Enigmail.perferGpgPath = " + gpgPath + "\n");
+ gPreferredGpgPath = gpgPath;
+ },
+
+ overwriteEnvVar: function(envVar) {
+ let envLines = envVar.split(/\n/);
+
+ gOverwriteEnvVar = [];
+ for (let i = 0; i < envLines.length; i++) {
+ let j = envLines[i].indexOf("=");
+ if (j > 0) {
+ gOverwriteEnvVar[envLines[i].substr(0, j)] = envLines[i].substr(j + 1);
+ }
+ }
+ },
+
+ getService: function(win, startingPreferences) {
+ if (!win) {
+ win = getEnigmailWindows().getBestParentWin();
+ }
+
+ getEnigmailLog().DEBUG("core.jsm: svc = " + this + "\n");
+
+ if (!this.initialized) {
+ const firstInitialization = !this.initializationAttempted;
+
+ try {
+ // Initialize enigmail
+ EnigmailCore.init(getEnigmailApp().getVersion());
+ this.initialize(win, getEnigmailApp().getVersion());
+
+ try {
+ // Reset alert count to default value
+ getEnigmailPrefs().getPrefBranch().clearUserPref("initAlert");
+ } catch (ex) {}
+ } catch (ex) {
+ if (firstInitialization) {
+ // Display initialization error alert
+ const errMsg = (this.initializationError ? this.initializationError : getEnigmailLocale().getString("accessError")) +
+ "\n\n" + getEnigmailLocale().getString("initErr.howToFixIt");
+
+ const checkedObj = {
+ value: false
+ };
+ if (getEnigmailPrefs().getPref("initAlert")) {
+ const r = getEnigmailDialog().longAlert(win, "Enigmail: " + errMsg,
+ getEnigmailLocale().getString("dlgNoPrompt"),
+ null, getEnigmailLocale().getString("initErr.setupWizard.button"),
+ null, checkedObj);
+ if (r >= 0 && checkedObj.value) {
+ getEnigmailPrefs().setPref("initAlert", false);
+ }
+ if (r == 1) {
+ // start setup wizard
+ getEnigmailWindows().openSetupWizard(win, false);
+ return Enigmail.getService(win);
+ }
+ }
+ if (getEnigmailPrefs().getPref("initAlert")) {
+ this.initializationAttempted = false;
+ gEnigmailService = null;
+ }
+ }
+
+ return null;
+ }
+
+ const configuredVersion = getEnigmailPrefs().getPref("configuredVersion");
+
+ getEnigmailLog().DEBUG("core.jsm: getService: last used version: " + configuredVersion + "\n");
+
+ if (firstInitialization && this.initialized &&
+ getEnigmailGpgAgent().agentType === "pgp") {
+ getEnigmailDialog().alert(win, getEnigmailLocale().getString("pgpNotSupported"));
+ }
+
+ if (this.initialized && (getEnigmailApp().getVersion() != configuredVersion)) {
+ getEnigmailConfigure().configureEnigmail(win, startingPreferences);
+ }
+ }
+
+ return this.initialized ? this : null;
+ }
+}; // Enigmail.prototype
+
+
+class Factory {
+ constructor(component) {
+ this.component = component;
+ this.register();
+ Object.freeze(this);
+ }
+
+ createInstance(outer, iid) {
+ if (outer) {
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+ }
+ return new this.component();
+ }
+
+ register() {
+ Cm.registerFactory(this.component.prototype.classID,
+ this.component.prototype.classDescription,
+ this.component.prototype.contractID,
+ this);
+ }
+
+ unregister() {
+ Cm.unregisterFactory(this.component.prototype.classID, this);
+ }
+}
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/cryptoAPI.jsm
@@ -0,0 +1,26 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailCryptoAPI"];
+
+var gCurrentApi = null;
+var Services = ChromeUtils.import("resource://gre/modules/Services.jsm").Services;
+
+
+function EnigmailCryptoAPI() {
+ if (!gCurrentApi) {
+ const {
+ getGnuPGAPI
+ } = ChromeUtils.import("chrome://openpgp/content/modules/cryptoAPI/gnupg.js");
+
+ gCurrentApi = getGnuPGAPI();
+ }
+
+ return gCurrentApi;
+}
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/cryptoAPI/gnupg-decryption.jsm
@@ -0,0 +1,376 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["GnuPGDecryption"];
+
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+const EnigmailDialog = ChromeUtils.import("chrome://openpgp/content/modules/dialog.jsm").EnigmailDialog;
+const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
+const EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs.jsm").EnigmailPrefs;
+const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
+const EnigmailExecution = ChromeUtils.import("chrome://openpgp/content/modules/execution.jsm").EnigmailExecution;
+const EnigmailErrorHandling = ChromeUtils.import("chrome://openpgp/content/modules/errorHandling.jsm").EnigmailErrorHandling;
+const EnigmailKey = ChromeUtils.import("chrome://openpgp/content/modules/key.jsm").EnigmailKey;
+const EnigmailKeyRing = ChromeUtils.import("chrome://openpgp/content/modules/keyRing.jsm").EnigmailKeyRing;
+const EnigmailGpg = ChromeUtils.import("chrome://openpgp/content/modules/gpg.jsm").EnigmailGpg;
+const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
+
+const STATUS_ERROR = EnigmailConstants.BAD_SIGNATURE | EnigmailConstants.DECRYPTION_FAILED;
+const STATUS_DECRYPTION_OK = EnigmailConstants.DECRYPTION_OKAY;
+const STATUS_GOODSIG = EnigmailConstants.GOOD_SIGNATURE;
+
+var GnuPGDecryption = {
+
+ /*
+ * options:
+ * - logFile (the actual file)
+ * - keyserver
+ * - keyserverProxy
+ * - fromAddr
+ * - noOutput
+ * - verifyOnly
+ * - mimeSignatureFile
+ * - maxOutputLength
+ *
+ */
+ getDecryptionArgs: function(options) {
+ var args = EnigmailGpg.getStandardArgs(true);
+
+ args.push("--log-file");
+ args.push(EnigmailFiles.getEscapedFilename(EnigmailFiles.getFilePath(options.logFile)));
+
+ if (options.keyserver && options.keyserver !== "") {
+ var keyserver = options.keyserver.trim();
+ args.push("--keyserver-options");
+ var keySrvArgs = "auto-key-retrieve";
+ var srvProxy = options.keyserverProxy;
+ if (srvProxy) {
+ keySrvArgs += ",http-proxy=" + srvProxy;
+ }
+ args.push(keySrvArgs);
+ args.push("--keyserver");
+ args.push(keyserver);
+ }
+
+ if (EnigmailGpg.getGpgFeature("supports-sender") && options.fromAddr) {
+ args.push("--sender");
+ args.push(options.fromAddr.toLowerCase());
+ }
+
+ if (options.noOutput) {
+ args.push("--verify");
+ if (options.mimeSignatureFile) {
+ args.push(options.mimeSignatureFile);
+ args.push("-");
+ }
+ }
+ else {
+ if (options.maxOutputLength) {
+ args.push("--max-output");
+ args.push(String(options.maxOutputLength));
+ }
+
+ args.push("--decrypt");
+ }
+
+ return args;
+ },
+ decryptMessageEnd: function(stderrStr, exitCode, outputLen, verifyOnly, noOutput, uiFlags, retStatusObj) {
+ EnigmailLog.DEBUG("gnupg-decryption.jsm: decryptMessageEnd: uiFlags=" + uiFlags + ", verifyOnly=" + verifyOnly + ", noOutput=" + noOutput + "\n");
+
+ stderrStr = stderrStr.replace(/\r\n/g, "\n");
+ EnigmailLog.DEBUG("gnupg-decryption.jsm: decryptMessageEnd: stderrStr=\n" + stderrStr + "\n");
+
+
+ var interactive = uiFlags & EnigmailConstants.UI_INTERACTIVE;
+ var pgpMime = uiFlags & EnigmailConstants.UI_PGP_MIME;
+ var allowImport = uiFlags & EnigmailConstants.UI_ALLOW_KEY_IMPORT;
+ var unverifiedEncryptedOK = uiFlags & EnigmailConstants.UI_UNVERIFIED_ENC_OK;
+ var j;
+
+ retStatusObj.statusFlags = 0;
+ retStatusObj.errorMsg = "";
+ retStatusObj.blockSeparation = "";
+
+ var errorMsg = EnigmailErrorHandling.parseErrorOutput(stderrStr, retStatusObj);
+ if (retStatusObj.statusFlags & STATUS_ERROR) {
+ retStatusObj.errorMsg = errorMsg;
+ }
+ else {
+ retStatusObj.errorMsg = "";
+ }
+
+ if (pgpMime) {
+ retStatusObj.statusFlags |= verifyOnly ? EnigmailConstants.PGP_MIME_SIGNED : EnigmailConstants.PGP_MIME_ENCRYPTED;
+ }
+
+ var statusMsg = retStatusObj.statusMsg;
+ exitCode = EnigmailExecution.fixExitCode(exitCode, retStatusObj);
+ if ((exitCode === 0) && !noOutput && !outputLen &&
+ ((retStatusObj.statusFlags & (STATUS_DECRYPTION_OK | STATUS_GOODSIG)) === 0)) {
+ exitCode = -1;
+ }
+
+ if (retStatusObj.statusFlags & EnigmailConstants.DISPLAY_MESSAGE && retStatusObj.extendedStatus.search(/\bdisp:/) >= 0) {
+ EnigmailDialog.alert(null, statusMsg);
+ return -1;
+ }
+
+ var errLines;
+ if (statusMsg) {
+ errLines = statusMsg.split(/\r?\n/);
+ }
+ else {
+ // should not really happen ...
+ errLines = stderrStr.split(/\r?\n/);
+ }
+
+ // possible STATUS Patterns (see GPG dod DETAILS.txt):
+ // one of these should be set for a signature:
+ var newsigPat = /^NEWSIG ?.*$/i;
+ var trustedsigPat = /^TRUST_(FULLY|ULTIMATE) ?.*$/i;
+ var goodsigPat = /^GOODSIG (\w{16}) (.*)$/i;
+ var badsigPat = /^BADSIG (\w{16}) (.*)$/i;
+ var expsigPat = /^EXPSIG (\w{16}) (.*)$/i;
+ var expkeysigPat = /^EXPKEYSIG (\w{16}) (.*)$/i;
+ var revkeysigPat = /^REVKEYSIG (\w{16}) (.*)$/i;
+ var errsigPat = /^ERRSIG (\w{16}) (.*)$/i;
+ // additional infos for good signatures:
+ var validSigPat = /^VALIDSIG (\w+) (.*) (\d+) (.*)/i;
+ // hint for a certain key id:
+ var userIdHintPat = /^USERID_HINT (\w{16}) (.*)$/i;
+ // to find out for which recipients the email was encrypted:
+ var encToPat = /^ENC_TO (\w{16}) (.*)$/i;
+
+ var matches;
+
+ var signed = false;
+ var goodOrExpOrRevSignature = false;
+ var sigKeyId = ""; // key of sender
+ var sigUserId = ""; // user ID of sender
+ var sigDetails = "";
+ var sigTrusted = false;
+ var encToDetails = "";
+ var encToArray = []; // collect ENC_TO lines here
+
+ for (j = 0; j < errLines.length; j++) {
+ EnigmailLog.DEBUG("gnupg-decryption.jsm: decryptMessageEnd: process: " + errLines[j] + "\n");
+
+ // ENC_TO entry
+ // - collect them for later processing to print details
+ matches = errLines[j].match(encToPat);
+ if (matches && (matches.length > 2)) {
+ encToArray.push("0x" + matches[1]);
+ }
+
+ // USERID_HINT entry
+ // - NOTE: NO END of loop
+ // ERROR: wrong to set userId because ecom is NOT the sender:
+ //matches = errLines[j].match(userIdHintPat);
+ //if (matches && (matches.length > 2)) {
+ // sigKeyId = matches[1];
+ // sigUserId = matches[2];
+ //}
+
+ // check for one of the possible SIG entries:
+
+ matches = errLines[j].match(newsigPat);
+ if (matches) {
+ if (signed) {
+ EnigmailLog.DEBUG("gnupg-decryption.jsm: decryptMessageEnd: multiple SIGN entries - ignoring previous signature\n");
+ }
+ signed = true;
+ goodOrExpOrRevSignature = false;
+ sigKeyId = "";
+ sigUserId = "";
+ sigDetails = "";
+ sigTrusted = false;
+ continue;
+ }
+
+ matches = errLines[j].match(trustedsigPat);
+ if (matches) {
+ sigTrusted = true;
+ continue;
+ }
+
+ matches = errLines[j].match(validSigPat);
+ if (matches && (matches.length > 4)) {
+ if (matches[4].length == 40) {
+ // in case of several subkeys refer to the main key ID.
+ // Only works with PGP V4 keys (Fingerprint length ==40)
+ sigKeyId = matches[4];
+ }
+ if (matches && (matches.length > 2)) {
+ sigDetails = errLines[j].substr(9);
+ }
+ continue;
+ }
+
+ // GOODSIG entry
+ matches = errLines[j].match(goodsigPat);
+ if (matches && (matches.length > 2)) {
+ if (signed) {
+ EnigmailLog.DEBUG("gnupg-decryption.jsm: decryptMessageEnd: OOPS: multiple SIGN entries\n");
+ }
+ signed = true;
+ goodOrExpOrRevSignature = true;
+ sigKeyId = matches[1];
+ sigUserId = matches[2];
+ }
+ else {
+ // BADSIG entry => signature found but bad
+ matches = errLines[j].match(badsigPat);
+ if (matches && (matches.length > 2)) {
+ if (signed) {
+ EnigmailLog.DEBUG("gnupg-decryption.jsm: decryptMessageEnd: OOPS: multiple SIGN entries\n");
+ }
+ signed = true;
+ goodOrExpOrRevSignature = false;
+ sigKeyId = matches[1];
+ sigUserId = matches[2];
+ }
+ else {
+ // EXPSIG entry => expired signature found
+ matches = errLines[j].match(expsigPat);
+ if (matches && (matches.length > 2)) {
+ if (signed) {
+ EnigmailLog.DEBUG("gnupg-decryption.jsm: decryptMessageEnd: OOPS: multiple SIGN entries\n");
+ }
+ signed = true;
+ goodOrExpOrRevSignature = true;
+ sigKeyId = matches[1];
+ sigUserId = matches[2];
+ }
+ else {
+ // EXPKEYSIG entry => signature found but key expired
+ matches = errLines[j].match(expkeysigPat);
+ if (matches && (matches.length > 2)) {
+ if (signed) {
+ EnigmailLog.DEBUG("gnupg-decryption.jsm: decryptMessageEnd: OOPS: multiple SIGN entries\n");
+ }
+ signed = true;
+ goodOrExpOrRevSignature = true;
+ sigKeyId = matches[1];
+ sigUserId = matches[2];
+ }
+ else {
+ // REVKEYSIG entry => signature found but key revoked
+ matches = errLines[j].match(revkeysigPat);
+ if (matches && (matches.length > 2)) {
+ if (signed) {
+ EnigmailLog.DEBUG("gnupg-decryption.jsm: decryptMessageEnd: OOPS: multiple SIGN entries\n");
+ }
+ signed = true;
+ goodOrExpOrRevSignature = true;
+ sigKeyId = matches[1];
+ sigUserId = matches[2];
+ }
+ else {
+ // ERRSIG entry => signature found but key not usable or unavailable
+ matches = errLines[j].match(errsigPat);
+ if (matches && (matches.length > 2)) {
+ if (signed) {
+ EnigmailLog.DEBUG("gnupg-decryption.jsm: decryptMessageEnd: OOPS: multiple SIGN entries\n");
+ }
+ signed = true;
+ goodOrExpOrRevSignature = false;
+ sigKeyId = matches[1];
+ // no user id with ecom istatus entry
+ }
+ }
+ }
+ }
+ }
+ }
+
+ } // end loop of processing errLines
+
+ if (sigTrusted) {
+ retStatusObj.statusFlags |= EnigmailConstants.TRUSTED_IDENTITY;
+ }
+
+ if (sigUserId && sigKeyId && EnigmailPrefs.getPref("displaySecondaryUid")) {
+ let keyObj = EnigmailKeyRing.getKeyById(sigKeyId);
+ if (keyObj) {
+ if (keyObj.photoAvailable) {
+ retStatusObj.statusFlags |= EnigmailConstants.PHOTO_AVAILABLE;
+ }
+ sigUserId = EnigmailKeyRing.getValidUids(sigKeyId).join("\n");
+ }
+ }
+ else if (sigUserId) {
+ sigUserId = EnigmailData.convertToUnicode(sigUserId, "UTF-8");
+ }
+
+ // add list of keys used for encryption if known (and their user IDs) if known
+ // Parsed status messages are something like (here the German version):
+ // [GNUPG:] ENC_TO AAAAAAAAAAAAAAAA 1 0
+ // [GNUPG:] ENC_TO 5B820D2D4553884F 16 0
+ // [GNUPG:] ENC_TO 37904DF2E631552F 1 0
+ // [GNUPG:] ENC_TO BBBBBBBBBBBBBBBB 1 0
+ // gpg: verschlüsselt mit 3072-Bit RSA Schlüssel, ID BBBBBBBB, erzeugt 2009-11-28
+ // "Joe Doo <joe.doo@domain.de>"
+ // [GNUPG:] NO_SECKEY E71712DF47BBCC40
+ // gpg: verschlüsselt mit RSA Schlüssel, ID AAAAAAAA
+ // [GNUPG:] NO_SECKEY AAAAAAAAAAAAAAAA
+ if (encToArray.length > 0) {
+ // for each key also show an associated user ID if known:
+ for (var encIdx = 0; encIdx < encToArray.length; ++encIdx) {
+ var localKeyId = encToArray[encIdx];
+ // except for ID 00000000, which signals hidden keys
+ if (localKeyId != "0x0000000000000000") {
+ let localKey = EnigmailKeyRing.getKeyById(localKeyId);
+ if (localKey) {
+ encToArray[encIdx] += " (" + localKey.userId + ")";
+ }
+ }
+ else {
+ encToArray[encIdx] = EnigmailLocale.getString("hiddenKey");
+ }
+ }
+ encToDetails = "\n " + encToArray.join(",\n ") + "\n";
+ }
+
+ retStatusObj.userId = sigUserId;
+ retStatusObj.keyId = sigKeyId;
+ retStatusObj.sigDetails = sigDetails;
+ retStatusObj.encToDetails = encToDetails;
+
+ if (signed) {
+ if (goodOrExpOrRevSignature) {
+ retStatusObj.errorMsg = EnigmailLocale.getString("prefGood", [sigUserId]);
+ /* + ", " + EnigmailLocale.getString("keyId") + " 0x" + sigKeyId.substring(8,16); */
+ }
+ else {
+ if (sigUserId.length > 0) {
+ retStatusObj.errorMsg = EnigmailLocale.getString("prefBad", [sigUserId]);
+ }
+ if (!exitCode)
+ exitCode = 1;
+ }
+ }
+
+ if (retStatusObj.statusFlags & EnigmailConstants.UNVERIFIED_SIGNATURE) {
+ retStatusObj.keyId = EnigmailKey.extractPubkey(statusMsg);
+
+ if (retStatusObj.statusFlags & EnigmailConstants.DECRYPTION_OKAY) {
+ exitCode = 0;
+ }
+ }
+
+ if (exitCode !== 0) {
+ // Error processing
+ EnigmailLog.DEBUG("gnupg-decryption.jsm: decryptMessageEnd: command execution exit code: " + exitCode + "\n");
+ }
+
+ return exitCode;
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/cryptoAPI/gnupg-key.jsm
@@ -0,0 +1,121 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/****
+ Private sub-module to gnupg.js for handling key import/export
+ ****/
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["GnuPG_importKeyFromFile", "GnuPG_extractSecretKey"];
+
+const EnigmailExecution = ChromeUtils.import("chrome://openpgp/content/modules/execution.jsm").EnigmailExecution;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailGpg = ChromeUtils.import("chrome://openpgp/content/modules/gpg.jsm").EnigmailGpg;
+const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailLocale;
+
+
+async function GnuPG_importKeyFromFile(inputFile) {
+ EnigmailLog.DEBUG("gnupg-key.jsm: importKeysFromFile: fileName=" + inputFile.path + "\n");
+ var command = EnigmailGpg.agentPath;
+ var args = EnigmailGpg.getStandardArgs(false).concat(["--no-tty", "--batch", "--no-verbose", "--status-fd", "2", "--no-auto-check-trustdb", "--import"]);
+
+ var fileName = EnigmailFiles.getEscapedFilename((inputFile.QueryInterface(Ci.nsIFile)).path);
+
+ args.push(fileName);
+
+ let res = await EnigmailExecution.execAsync(command, args, "");
+ let statusMsg = res.statusMsg;
+
+ var keyList = [];
+ let importedKeys = [];
+ let importSum = 0;
+ let importUnchanged = 0;
+
+ // IMPORT_RES <count> <no_user_id> <imported> 0 <unchanged>
+ // <n_uids> <n_subk> <n_sigs> <n_revoc> <sec_read> <sec_imported> <sec_dups> <not_imported>
+ if (statusMsg) {
+ let import_res = statusMsg.match(/^IMPORT_RES ([0-9]+) ([0-9]+) ([0-9]+) 0 ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)/m);
+
+ if (import_res !== null) {
+ let secCount = parseInt(import_res[9], 10); // number of secret keys found
+ let secImported = parseInt(import_res[10], 10); // number of secret keys imported
+ let secDups = parseInt(import_res[11], 10); // number of secret keys already on the keyring
+
+ if (secCount !== secImported + secDups) {
+ res.errorMsg = EnigmailLocale.getString("import.secretKeyImportError");
+ res.exitCode = 1;
+ }
+ else {
+ importSum = parseInt(import_res[1], 10);
+ importUnchanged = parseInt(import_res[4], 10);
+ res.exitCode = 0;
+ var statusLines = statusMsg.split(/\r?\n/);
+
+ for (let j = 0; j < statusLines.length; j++) {
+ var matches = statusLines[j].match(/IMPORT_OK ([0-9]+) (\w+)/);
+ if (matches && (matches.length > 2)) {
+ if (typeof (keyList[matches[2]]) != "undefined") {
+ keyList[matches[2]] |= Number(matches[1]);
+ }
+ else
+ keyList[matches[2]] = Number(matches[1]);
+
+ importedKeys.push(matches[2]);
+ EnigmailLog.DEBUG("gnupg-key.jsm: importKeysFromFile: imported " + matches[2] + ":" + matches[1] + "\n");
+ }
+ }
+ }
+ }
+ }
+
+ return {
+ exitCode: res.exitCode,
+ errorMsg: res.errorMsg,
+ importedKeys: importedKeys,
+ importSum: importSum,
+ importUnchanged: importUnchanged
+ };
+}
+
+
+async function GnuPG_extractSecretKey(userId, minimalKey) {
+ let args = EnigmailGpg.getStandardArgs(true);
+ let exitCode = -1,
+ errorMsg = "";
+
+ if (minimalKey) {
+ args.push("--export-options");
+ args.push("export-minimal,no-export-attributes");
+ }
+
+ args.push("-a");
+ args.push("--export-secret-keys");
+
+ if (userId) {
+ args = args.concat(userId.split(/[ ,\t]+/));
+ }
+
+ let res = await EnigmailExecution.execAsync(EnigmailGpg.agentPath, args, "");
+
+ if (res.stdoutData) {
+ exitCode = 0;
+ }
+
+ if (exitCode !== 0) {
+ if (res.errorMsg) {
+ errorMsg = EnigmailFiles.formatCmdLine(EnigmailGpg.agentPath, args);
+ errorMsg += "\n" + res.errorMsg;
+ }
+ }
+
+ return {
+ keyData: res.stdoutData,
+ exitCode: exitCode,
+ errorMsg: errorMsg
+ };
+}
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/cryptoAPI/gnupg-keylist.jsm
@@ -0,0 +1,503 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/****
+ Private sub-module to gnupg.js for handling key lists from GnuPG
+ ****/
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["obtainKeyList", "createKeyObj",
+ "getPhotoFileFromGnuPG", "extractSignatures", "getGpgKeyData"
+];
+
+const EnigmailTime = ChromeUtils.import("chrome://openpgp/content/modules/time.jsm").EnigmailTime;
+const EnigmailGpg = ChromeUtils.import("chrome://openpgp/content/modules/gpg.jsm").EnigmailGpg;
+const EnigmailExecution = ChromeUtils.import("chrome://openpgp/content/modules/execution.jsm").EnigmailExecution;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailTrust = ChromeUtils.import("chrome://openpgp/content/modules/trust.jsm").EnigmailTrust;
+const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+const EnigmailOS = ChromeUtils.import("chrome://openpgp/content/modules/os.jsm").EnigmailOS;
+const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
+
+// field ID's of key list (as described in the doc/DETAILS file in the GnuPG distribution)
+const ENTRY_ID = 0;
+const KEY_TRUST_ID = 1;
+const KEY_SIZE_ID = 2;
+const KEY_ALGO_ID = 3;
+const KEY_ID = 4;
+const CREATED_ID = 5;
+const EXPIRY_ID = 6;
+const UID_ID = 7;
+const OWNERTRUST_ID = 8;
+const USERID_ID = 9;
+const SIG_TYPE_ID = 10;
+const KEY_USE_FOR_ID = 11;
+
+const ALGO_SYMBOL = {
+ 1: "RSA",
+ 2: "RSA",
+ 3: "RSA",
+ 16: "ELG",
+ 17: "DSA",
+ 18: "ECDH",
+ 19: "ECDSA",
+ 20: "ELG",
+ 22: "EDDSA"
+};
+
+const UNKNOWN_SIGNATURE = "[User ID not found]";
+
+const NS_RDONLY = 0x01;
+const NS_WRONLY = 0x02;
+const NS_CREATE_FILE = 0x08;
+const NS_TRUNCATE = 0x20;
+const STANDARD_FILE_PERMS = 0o600;
+
+const NS_LOCALFILEOUTPUTSTREAM_CONTRACTID = "@mozilla.org/network/file-output-stream;1";
+
+/**
+ * Get key list from GnuPG.
+ *
+ * @param {Array of String} onlyKeys: only load data for specified key IDs
+ *
+ * @return {Promise<Array Object>}:
+ * key objects as specified in EnigmailKeyObj.constructor
+ */
+async function obtainKeyList(onlyKeys = null) {
+ EnigmailLog.DEBUG("gnupg-keylist.jsm: obtainKeyList()\n");
+
+ let secKeyList = [],
+ pubKeyList = [];
+ let commonArgs = EnigmailGpg.getStandardArgs(true);
+ commonArgs = commonArgs.concat(["--with-fingerprint", "--fixed-list-mode", "--with-colons"]);
+
+ let args = commonArgs.concat(["--list-keys"]);
+ if (onlyKeys) {
+ args = args.concat(onlyKeys);
+ }
+
+ let res = await EnigmailExecution.execAsync(EnigmailGpg.agentPath, args, "");
+ pubKeyList = res.stdoutData.split(/\n/);
+
+ let keyList = {
+ keys: [],
+ index: []
+ };
+
+ EnigmailLog.DEBUG(`gnupg-keylist.jsm: obtainKeyList: #lines: ${pubKeyList.length}\n`);
+ if (pubKeyList.length > 0) {
+ appendKeyItems(pubKeyList, keyList);
+
+ args = commonArgs.concat(["--list-secret-keys"]);
+ if (onlyKeys) {
+ args = args.concat(onlyKeys);
+ }
+
+ res = await EnigmailExecution.execAsync(EnigmailGpg.agentPath, args, "");
+ secKeyList = res.stdoutData.split(/\n/);
+ appendKeyItems(secKeyList, keyList);
+ }
+
+ return keyList;
+}
+
+
+/**
+ * Append key objects to a given key cache
+ *
+ * @param keyListString: array of |string| formatted output from GnuPG for key listing
+ * @param keyList: |object| holding the resulting key list
+ * obj.keyList: Array holding key objects
+ * obj.keySortList: Array holding values to make sorting easier
+ *
+ * no return value
+ */
+function appendKeyItems(keyListString, keyList) {
+ EnigmailLog.DEBUG("gnupg-keylist.jsm: appendKeyItems()\n");
+ let keyObj = {};
+ let uatNum = 0; // counter for photos (counts per key)
+
+ const TRUSTLEVELS_SORTED = EnigmailTrust.trustLevelsSorted();
+
+ for (let i = 0; i < keyListString.length; i++) {
+ let listRow = keyListString[i].split(/:/);
+ if (listRow.length === 0) continue;
+
+ switch (listRow[ENTRY_ID]) {
+ case "pub":
+ keyObj = createKeyObj(listRow);
+ uatNum = 0;
+ keyList.keys.push(keyObj);
+ keyList.index[keyObj.keyId] = keyObj;
+ break;
+ case "sec":
+ keyObj = keyList.index[listRow[KEY_ID]];
+ if (keyObj) {
+ keyObj.secretAvailable = true;
+ // create a dummy object that is not added to the list since we already have the key
+ keyObj = createKeyObj(listRow);
+ } else {
+ appendUnkownSecretKey(listRow[KEY_ID], keyListString, i, keyList);
+ keyObj = keyList.index[listRow[KEY_ID]];
+ keyObj.secretAvailable = true;
+ }
+ break;
+ case "fpr":
+ // only take first fpr line, this is the fingerprint of the primary key and what we want
+ if (keyObj.fpr === "") {
+ keyObj.fpr = listRow[USERID_ID];
+ }
+ break;
+ case "uid":
+ if (listRow[USERID_ID].length === 0) {
+ listRow[USERID_ID] = "-";
+ }
+ if (typeof(keyObj.userId) !== "string") {
+ keyObj.userId = EnigmailData.convertGpgToUnicode(listRow[USERID_ID]);
+ if (TRUSTLEVELS_SORTED.indexOf(listRow[KEY_TRUST_ID]) < TRUSTLEVELS_SORTED.indexOf(keyObj.keyTrust)) {
+ // reduce key trust if primary UID is less trusted than public key
+ keyObj.keyTrust = listRow[KEY_TRUST_ID];
+ }
+ }
+
+ keyObj.userIds.push({
+ userId: EnigmailData.convertGpgToUnicode(listRow[USERID_ID]),
+ keyTrust: listRow[KEY_TRUST_ID],
+ uidFpr: listRow[UID_ID],
+ type: "uid"
+ });
+
+ break;
+ case "sub":
+ keyObj.subKeys.push({
+ keyId: listRow[KEY_ID],
+ expiry: EnigmailTime.getDateTime(listRow[EXPIRY_ID], true, false),
+ expiryTime: Number(listRow[EXPIRY_ID]),
+ keyTrust: listRow[KEY_TRUST_ID],
+ keyUseFor: listRow[KEY_USE_FOR_ID],
+ keySize: listRow[KEY_SIZE_ID],
+ algoSym: ALGO_SYMBOL[listRow[KEY_ALGO_ID]],
+ created: EnigmailTime.getDateTime(listRow[CREATED_ID], true, false),
+ keyCreated: Number(listRow[CREATED_ID]),
+ type: "sub"
+ });
+ break;
+ case "uat":
+ if (listRow[USERID_ID].indexOf("1 ") === 0) {
+ const userId = EnigmailLocale.getString("userAtt.photo");
+ keyObj.userIds.push({
+ userId: userId,
+ keyTrust: listRow[KEY_TRUST_ID],
+ uidFpr: listRow[UID_ID],
+ type: "uat",
+ uatNum: uatNum
+ });
+ keyObj.photoAvailable = true;
+ ++uatNum;
+ }
+ break;
+ }
+ }
+}
+
+function createKeyObj(lineArr) {
+ let keyObj = {};
+ if (lineArr[ENTRY_ID] === "pub" || lineArr[ENTRY_ID] === "sec") {
+ keyObj.keyId = lineArr[KEY_ID];
+ keyObj.expiryTime = Number(lineArr[EXPIRY_ID]);
+ keyObj.created = EnigmailTime.getDateTime(lineArr[CREATED_ID], true, false);
+ keyObj.keyCreated = Number(lineArr[CREATED_ID]);
+ keyObj.keyTrust = lineArr[KEY_TRUST_ID];
+ keyObj.keyUseFor = lineArr[KEY_USE_FOR_ID];
+ keyObj.ownerTrust = lineArr[OWNERTRUST_ID];
+ keyObj.algoSym = ALGO_SYMBOL[lineArr[KEY_ALGO_ID]];
+ keyObj.keySize = lineArr[KEY_SIZE_ID];
+ keyObj.userIds = [];
+ keyObj.subKeys = [];
+ keyObj.fpr = "";
+ keyObj.userId = null;
+ keyObj.photoAvailable = false;
+ } else if (lineArr[ENTRY_ID] === "grp") {
+ keyObj.keyUseFor = "G";
+ keyObj.userIds = [];
+ keyObj.subKeys = [];
+ }
+ keyObj.type = lineArr[ENTRY_ID];
+
+ return keyObj;
+}
+
+
+/**
+ * Handle secret keys for which gpg 2.0 does not create a public key record
+ */
+function appendUnkownSecretKey(keyId, aKeyList, startIndex, keyList) {
+ EnigmailLog.DEBUG(`gnupg-keylist.jsm: appendUnkownSecretKey: keyId: ${keyId}\n`);
+
+ let keyListStr = [];
+
+ for (let j = startIndex; j < aKeyList.length && (j === startIndex || aKeyList[j].substr(0, 4) !== "sec:"); j++) {
+ keyListStr.push(aKeyList[j]);
+ }
+
+ // make the listing a "public" key
+ keyListStr[0] = keyListStr[0].replace(/^sec:/, "pub:");
+
+ appendKeyItems(keyListStr, keyList);
+}
+
+
+/**
+ * Extract a photo ID from a key, store it as file and return the file object.
+
+ * @param {String} keyId: Key ID / fingerprint
+ * @param {Number} photoNumber: number of the photo on the key, starting with 0
+ *
+ * @return {Promise<nsIFile>} object or null in case no data / error.
+ */
+async function getPhotoFileFromGnuPG(keyId, photoNumber) {
+ EnigmailLog.DEBUG(`gnupg-keylist.jsm: getPhotoFileFromGnuPG, keyId=${keyId} photoNumber=${photoNumber}\n`);
+
+ const GPG_ADDITIONAL_OPTIONS = ["--no-secmem-warning", "--no-verbose", "--no-auto-check-trustdb",
+ "--batch", "--no-tty", "--no-verbose", "--status-fd", "1", "--attribute-fd", "2",
+ "--fixed-list-mode", "--list-keys", keyId
+ ];
+ const args = EnigmailGpg.getStandardArgs(false).concat(GPG_ADDITIONAL_OPTIONS);
+
+ let res = await EnigmailExecution.execAsync(EnigmailGpg.agentPath, args);
+ let photoData = res.stderrData;
+ let outputTxt = res.stdoutData;
+
+ if (!outputTxt || !photoData) {
+ return null;
+ }
+
+ if (EnigmailOS.isDosLike && EnigmailGpg.getGpgFeature("windows-photoid-bug")) {
+ // workaround for error in gpg
+ photoData = photoData.replace(/\r\n/g, "\n");
+ }
+
+ // [GNUPG:] ATTRIBUTE A053069284158FC1E6770BDB57C9EB602B0717E2 2985
+ let foundPicture = -1;
+ let skipData = 0;
+ let imgSize = -1;
+ const statusLines = outputTxt.split(/[\n\r+]/);
+
+ for (let i = 0; i < statusLines.length; i++) {
+ const matches = statusLines[i].match(/\[GNUPG:\] ATTRIBUTE ([A-F\d]+) (\d+) (\d+) (\d+) (\d+) (\d+) (\d+) (\d+)/);
+ if (matches && matches[3] == "1") {
+ // attribute is an image
+ foundPicture++;
+ if (foundPicture === photoNumber) {
+ imgSize = Number(matches[2]);
+ break;
+ } else {
+ skipData += Number(matches[2]);
+ }
+ }
+ }
+
+ if (foundPicture >= 0 && foundPicture === photoNumber) {
+ if (photoData.search(/^gpg: /) === 0) {
+ // skip disturbing gpg output
+ let i = photoData.search(/\n/) + 1;
+ skipData += i;
+ }
+
+ const pictureData = photoData.substr(16 + skipData, imgSize);
+ if (!pictureData.length) {
+ return null;
+ }
+
+ try {
+ const flags = NS_WRONLY | NS_CREATE_FILE | NS_TRUNCATE;
+ const picFile = EnigmailFiles.getTempDirObj();
+
+ picFile.append(keyId + ".jpg");
+ picFile.createUnique(picFile.NORMAL_FILE_TYPE, STANDARD_FILE_PERMS);
+
+ const fileStream = Cc[NS_LOCALFILEOUTPUTSTREAM_CONTRACTID].createInstance(Ci.nsIFileOutputStream);
+ fileStream.init(picFile, flags, STANDARD_FILE_PERMS, 0);
+ if (fileStream.write(pictureData, pictureData.length) !== pictureData.length) {
+ fileStream.close();
+ throw Components.results.NS_ERROR_FAILURE;
+ }
+
+ fileStream.flush();
+ fileStream.close();
+
+ // delete picFile upon exit
+ let extAppLauncher = Cc["@mozilla.org/mime;1"].getService(Ci.nsPIExternalAppLauncher);
+ extAppLauncher.deleteTemporaryFileOnExit(picFile);
+ return picFile;
+ } catch (ex) {}
+ }
+ return null;
+}
+
+
+/**
+ * Return signatures for a given key list
+ *
+ * @param {String} gpgKeyList Output from gpg such as produced by getKeySig()
+ * Only the first public key is processed!
+ * @param {Boolean} ignoreUnknownUid true if unknown signer's UIDs should be filtered out
+ *
+ * @return {Array of Object}:
+ * - uid
+ * - uidLabel
+ * - creationDate
+ * - sigList: [uid, creationDate, signerKeyId, sigType ]
+ */
+
+function extractSignatures(gpgKeyList, ignoreUnknownUid) {
+ EnigmailLog.DEBUG("gnupg.js: extractSignatures\n");
+
+ var listObj = {};
+
+ let havePub = false;
+ let currUid = "",
+ keyId = "",
+ fpr = "";
+
+ const lineArr = gpgKeyList.split(/\n/);
+ for (let i = 0; i < lineArr.length; i++) {
+ // process lines such as:
+ // tru::1:1395895453:1442881280:3:1:5
+ // pub:f:4096:1:C1B875ED336XX959:2299509307:1546189300::f:::scaESCA:
+ // fpr:::::::::102A1C8CC524A966849C33D7C8B157EA336XX959:
+ // uid:f::::1388511201::67D5B96DC564598D4D4D9E0E89F5B83C9931A154::Joe Fox <joe@fox.com>:
+ // sig:::1:C8B157EA336XX959:2299509307::::Joe Fox <joe@fox.com>:13x:::::2:
+ // sub:e:2048:1:B214734F0F5C7041:1316219469:1199912694:::::e:
+ // sub:f:2048:1:70E7A471DABE08B0:1316221524:1546189300:::::s:
+ const lineTokens = lineArr[i].split(/:/);
+ switch (lineTokens[ENTRY_ID]) {
+ case "pub":
+ if (havePub) {
+ return listObj;
+ }
+ havePub = true;
+ keyId = lineTokens[KEY_ID];
+ break;
+ case "fpr":
+ if (fpr === "")
+ fpr = lineTokens[USERID_ID];
+ break;
+ case "uid":
+ case "uat":
+ currUid = lineTokens[UID_ID];
+ listObj[currUid] = {
+ userId: lineTokens[ENTRY_ID] == "uat" ? EnigmailLocale.getString("keyring.photo") : EnigmailData.convertGpgToUnicode(lineTokens[USERID_ID]),
+ rawUserId: lineTokens[USERID_ID],
+ keyId: keyId,
+ fpr: fpr,
+ created: EnigmailTime.getDateTime(lineTokens[CREATED_ID], true, false),
+ sigList: []
+ };
+ break;
+ case "sig":
+ if (lineTokens[SIG_TYPE_ID].substr(0, 2).toLowerCase() !== "1f") {
+ // ignrore revoked signature
+
+ let sig = {
+ userId: EnigmailData.convertGpgToUnicode(lineTokens[USERID_ID]),
+ created: EnigmailTime.getDateTime(lineTokens[CREATED_ID], true, false),
+ signerKeyId: lineTokens[KEY_ID],
+ sigType: lineTokens[SIG_TYPE_ID],
+ sigKnown: lineTokens[USERID_ID] != UNKNOWN_SIGNATURE
+ };
+
+ if (!ignoreUnknownUid || sig.userId != UNKNOWN_SIGNATURE) {
+ listObj[currUid].sigList.push(sig);
+ }
+ }
+ break;
+ }
+ }
+
+ return listObj;
+}
+
+
+async function getGpgKeyData(armorKeyString) {
+ EnigmailLog.DEBUG("gnupg.js: getGpgKeyData()\n");
+
+ if (!EnigmailGpg.getGpgFeature("supports-show-only")) {
+ throw "unsupported";
+ }
+
+ let args = EnigmailGpg.getStandardArgs(false).concat(["--no-tty", "--batch", "--no-verbose", "--with-fingerprint", "--with-colons", "--import-options", "import-show", "--dry-run", "--import"]);
+
+ let res = await EnigmailExecution.execAsync(EnigmailGpg.agentPath, args, armorKeyString);
+ let lines = res.stdoutData.split(/\n/);
+
+ let key = {};
+ let keyId = "";
+ let keyList = [];
+ /*
+ pub:u:256:22:84F83BE88C892606:1525969855:1683649855::u:::scESC:::::ed25519:::0:
+ fpr:::::::::AFE1B65C5F39ACA7960B22CD84F83BE88C892606:
+ uid:u::::1525969914::22DB32406212400B52CDC74DA2B33418637430F1::Patrick (ECC) <patrick@enigmail.net>::::::::::0:
+ uid:u::::1525969855::F70B7A77F085AA7BA003D6AFAB6FF0DB1FC901B0::enigmail <patrick@enigmail.net>::::::::::0:
+ sub:u:256:18:329DAB3350400C40:1525969855:1683649855:::::e:::::cv25519::
+ fpr:::::::::3B154538D4DFAA19BDADAAD0329DAB3350400C40:
+ */
+
+ for (let i = 0; i < lines.length; i++) {
+ const lineTokens = lines[i].split(/:/);
+
+ switch (lineTokens[ENTRY_ID]) {
+ case "pub":
+ case "sec":
+ key = {
+ id: lineTokens[KEY_ID],
+ fpr: null,
+ name: null,
+ isSecret: false,
+ created: EnigmailTime.getDateTime(lineTokens[CREATED_ID], true, false),
+ uids: []
+ };
+
+ if (!(key.id in keyList)) {
+ keyList[key.id] = key;
+ }
+
+ if (lineTokens[ENTRY_ID] === "sec") {
+ keyList[key.id].isSecret = true;
+ }
+ break;
+ case "fpr":
+ if (!key.fpr) {
+ key.fpr = lineTokens[USERID_ID];
+ }
+ break;
+ case "uid":
+ if (!key.name) {
+ key.name = lineTokens[USERID_ID];
+ }
+ else {
+ key.uids.push(lineTokens[USERID_ID]);
+ }
+ break;
+ case "rvs":
+ case "rvk":
+ keyId = lineTokens[KEY_ID];
+ if (keyId in keyList) {
+ keyList[keyId].revoke = true;
+ } else {
+ keyList[keyId] = {
+ revoke: true,
+ id: keyId
+ };
+ }
+ break;
+ }
+ }
+
+ return keyList;
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/cryptoAPI/gnupg.js
@@ -0,0 +1,418 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["getGnuPGAPI"];
+
+var Services = Components.utils.import("resource://gre/modules/Services.jsm").Services;
+
+Services.scriptloader.loadSubScript("chrome://openpgp/content/modules/cryptoAPI/interface.js",
+ null, "UTF-8"); /* global CryptoAPI */
+
+/* global getOpenPGP: false, EnigmailLog: false */
+
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailLazy = ChromeUtils.import("chrome://openpgp/content/modules/lazy.jsm").EnigmailLazy;
+const EnigmailGpg = ChromeUtils.import("chrome://openpgp/content/modules/gpg.jsm").EnigmailGpg;
+const EnigmailExecution = ChromeUtils.import("chrome://openpgp/content/modules/execution.jsm").EnigmailExecution;
+const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
+const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
+const EnigmailTime = ChromeUtils.import("chrome://openpgp/content/modules/time.jsm").EnigmailTime;
+const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+const EnigmailPassword = ChromeUtils.import("chrome://openpgp/content/modules/passwords.jsm").EnigmailPassword;
+const EnigmailErrorHandling = ChromeUtils.import("chrome://openpgp/content/modules/errorHandling.jsm").EnigmailErrorHandling;
+const GnuPGDecryption = ChromeUtils.import("chrome://openpgp/content/modules/cryptoAPI/gnupg-decryption.jsm").GnuPGDecryption;
+
+const {
+ obtainKeyList,
+ createKeyObj,
+ getPhotoFileFromGnuPG,
+ extractSignatures,
+ getGpgKeyData
+} = ChromeUtils.import("chrome://openpgp/content/modules/cryptoAPI/gnupg-keylist.jsm");
+
+const {
+ GnuPG_importKeyFromFile,
+ GnuPG_extractSecretKey
+} = ChromeUtils.import("chrome://openpgp/content/modules/cryptoAPI/gnupg-key.jsm");
+
+/**
+ * GnuPG implementation of CryptoAPI
+ */
+
+class GnuPGCryptoAPI extends CryptoAPI {
+ constructor() {
+ super();
+ this.api_name = "GnuPG";
+ }
+
+ /**
+ * Get the list of all knwn keys (including their secret keys)
+ * @param {Array of String} onlyKeys: [optional] only load data for specified key IDs
+ *
+ * @return {Promise<Array of Object>}
+ */
+ async getKeys(onlyKeys = null) {
+ let keyList = await obtainKeyList(onlyKeys);
+ return keyList.keys;
+ }
+
+ /**
+ * Get groups defined in gpg.conf in the same structure as KeyObject
+ *
+ * @return {Array of KeyObject} with type = "grp"
+ */
+ getGroups() {
+ let groups = EnigmailGpg.getGpgGroups();
+
+ let r = [];
+ for (var i = 0; i < groups.length; i++) {
+
+ let keyObj = createKeyObj(["grp"]);
+ keyObj.keyTrust = "g";
+ keyObj.userId = EnigmailData.convertGpgToUnicode(groups[i].alias).replace(/\\e3A/g, ":");
+ keyObj.keyId = keyObj.userId;
+ var grpMembers = EnigmailData.convertGpgToUnicode(groups[i].keylist).replace(/\\e3A/g, ":").split(/[,;]/);
+ for (var grpIdx = 0; grpIdx < grpMembers.length; grpIdx++) {
+ keyObj.userIds.push({
+ userId: grpMembers[grpIdx],
+ keyTrust: "q"
+ });
+ }
+ r.push(keyObj);
+ }
+
+ return r;
+ }
+
+
+ /**
+ * Obtain signatures for a given set of key IDs.
+ *
+ * @param {String} keyId: space-separated list of key IDs
+ * @param {Boolean} ignoreUnknownUid: if true, filter out unknown signer's UIDs
+ *
+ * @return {Promise<Array of Object>} - see extractSignatures()
+ */
+ async getKeySignatures(keyId, ignoreUnknownUid = false) {
+ EnigmailLog.DEBUG(`gnupg.js: getKeySignatures: ${keyId}\n`);
+
+ const args = EnigmailGpg.getStandardArgs(true).concat(["--with-fingerprint", "--fixed-list-mode", "--with-colons", "--list-sig"]).concat(keyId.split(" "));
+
+ let res = await EnigmailExecution.execAsync(EnigmailGpg.agentPath, args, "");
+
+ if (!(res.statusFlags & EnigmailConstants.BAD_SIGNATURE)) {
+ // ignore exit code as recommended by GnuPG authors
+ res.exitCode = 0;
+ }
+
+ if (res.exitCode !== 0) {
+ if (res.errorMsg) {
+ res.errorMsg += "\n" + EnigmailFiles.formatCmdLine(EnigmailGpg.agentPath, args);
+ res.errorMsg += "\n" + res.errorMsg;
+ }
+ return "";
+ }
+
+ if (res.stdoutData.length > 0) {
+ return extractSignatures(res.stdoutData, ignoreUnknownUid);
+ }
+ return null;
+ }
+
+
+ /**
+ * Export the minimum key for the public key object:
+ * public key, primary user ID, newest encryption subkey
+ *
+ * @param {String} fpr: a single FPR
+ * @param {String} email: [optional] the email address of the desired user ID.
+ * If the desired user ID cannot be found or is not valid, use the primary UID instead
+ * @param {Array<Number>} subkeyDates: [optional] remove subkeys with sepcific creation Dates
+ *
+ * @return {Promise<Object>}:
+ * - exitCode (0 = success)
+ * - errorMsg (if exitCode != 0)
+ * - keyData: BASE64-encded string of key data
+ */
+ async getMinimalPubKey(fpr, email, subkeyDates) {
+ EnigmailLog.DEBUG(`gnupg.js: getMinimalPubKey: ${fpr}\n`);
+
+ let retObj = {
+ exitCode: 0,
+ errorMsg: "",
+ keyData: ""
+ };
+ let minimalKeyBlock = null;
+
+ let args = EnigmailGpg.getStandardArgs(true);
+
+ if (EnigmailGpg.getGpgFeature("export-specific-uid")) {
+ // Use GnuPG filters if possible
+ let dropSubkeyFilter = "usage!~e && usage!~s";
+
+ if (subkeyDates && subkeyDates.length > 0) {
+ dropSubkeyFilter = subkeyDates.map(x => `key_created!=${x}`).join(" && ");
+ }
+ args = args.concat(["--export-options", "export-minimal,no-export-attributes",
+ "--export-filter", "keep-uid=" + (email ? "mbox=" + email : "primary=1"),
+ "--export-filter", "drop-subkey=" + dropSubkeyFilter,
+ "--export", fpr
+ ]);
+ } else {
+ args = args.concat(["--export-options", "export-minimal,no-export-attributes", "-a", "--export", fpr]);
+ }
+
+ const statusObj = {};
+ const exitCodeObj = {};
+ let res = await EnigmailExecution.execAsync(EnigmailGpg.agentPath, args);
+ let keyBlock = res.stdoutData;
+
+ // GnuPG 2.1.10+
+ if (!EnigmailGpg.getGpgFeature("export-result")) {
+ retObj.exitCode = 2;
+ retObj.errorMsg = EnigmailLocale.getString("failKeyExtract");
+ }
+
+ let r = new RegExp("^\\[GNUPG:\\] EXPORTED " + fpr, "m");
+ if (res.stderrData.search(r) < 0) {
+ retObj.exitCode = 2;
+ retObj.errorMsg = EnigmailLocale.getString("failKeyExtract");
+ }
+
+ retObj.keyData = btoa(keyBlock);
+ return retObj;
+ }
+
+ /**
+ * Extract a photo ID from a key, store it as file and return the file object.
+ *
+ * @param {String} keyId: Key ID / fingerprint
+ * @param {Number} photoNumber: number of the photo on the key, starting with 0
+ *
+ * @return {nsIFile} object or null in case no data / error.
+ */
+ async getPhotoFile(keyId, photoNumber) {
+ let file = await getPhotoFileFromGnuPG(keyId, photoNumber);
+ return file;
+ }
+
+ /**
+ * Import key(s) from a file
+ *
+ * @param {nsIFile} inputFile: the file holding the keys
+ *
+ * @return {Object} or null in case no data / error:
+ * - {Number} exitCode: result code (0: OK)
+ * - {Array of String) importedKeys: imported fingerprints
+ * - {String} errorMsg: human readable error message
+ * - {Number} importSum: total number of processed keys
+ * - {Number} importUnchanged: number of unchanged keys
+ */
+ async importKeyFromFile(inputFile) {
+ let keys = await GnuPG_importKeyFromFile(inputFile);
+ return keys;
+ }
+
+ /**
+ * Export secret key(s) to a file
+ *
+ * @param {String} keyId Specification by fingerprint or keyID
+ * @param {Boolean} minimalKey if true, reduce key to minimum required
+ *
+ * @return {Object}:
+ * - {Number} exitCode: result code (0: OK)
+ * - {String} keyData: ASCII armored key data material
+ * - {String} errorMsg: error message in case exitCode !== 0
+ */
+
+ async extractSecretKey(keyId, minimalKey) {
+ let ret = await GnuPG_extractSecretKey(keyId, minimalKey);
+
+ if (ret.exitCode !== 0) {
+ ret.errorMsg = EnigmailLocale.getString("failKeyExtract") + "\n" + ret.errorMsg;
+ }
+ return ret;
+ }
+
+ /**
+ *
+ * @param {byte} byteData The encrypted data
+ *
+ * @return {String or null} - the name of the attached file
+ */
+
+ async getFileName(byteData) {
+ EnigmailLog.DEBUG(`gnupg.js: getFileName()\n`);
+ const args = EnigmailGpg.getStandardArgs(true).concat(EnigmailPassword.command()).concat(["--decrypt"]);
+
+ let res = await EnigmailExecution.execAsync(EnigmailGpg.agentPath, args, byteData + "\n");
+
+ const matches = res.stderrData.match(/^(\[GNUPG:\] PLAINTEXT [0-9]+ [0-9]+ )(.*)$/m);
+ if (matches && (matches.length > 2)) {
+ var filename = matches[2];
+ if (filename.indexOf(" ") > 0) {
+ filename = filename.replace(/ .*$/, "");
+ }
+ return EnigmailData.convertToUnicode(unescape(filename), "utf-8");
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ *
+ * @param {Path} filePath The signed file
+ * @param {Path} sigPath The signature to verify
+ *
+ * @return {Promise<String>} - A message from the verification.
+ *
+ * Use Promise.catch to handle failed verifications.
+ * The message will be an error message in this case.
+ */
+
+ async verifyAttachment(filePath, sigPath) {
+ EnigmailLog.DEBUG(`gnupg.js: verifyAttachment()\n`);
+ const args = EnigmailGpg.getStandardArgs(true).concat(["--verify", sigPath, filePath]);
+ let result = await EnigmailExecution.execAsync(EnigmailGpg.agentPath, args);
+ const decrypted = {};
+ GnuPGDecryption.decryptMessageEnd(result.stderrData, result.exitCode, 1, true, true, EnigmailConstants.UI_INTERACTIVE, decrypted);
+ if (result.exitCode === 0) {
+ const detailArr = decrypted.sigDetails.split(/ /);
+ const dateTime = EnigmailTime.getDateTime(detailArr[2], true, true);
+ const msg1 = decrypted.errorMsg.split(/\n/)[0];
+ const msg2 = EnigmailLocale.getString("keyAndSigDate", ["0x" + decrypted.keyId, dateTime]);
+ const message = msg1 + "\n" + msg2;
+ return (message);
+ } else {
+ throw (decrypted.errorMsg);
+ }
+ }
+
+
+ /**
+ *
+ * @param {Bytes} encrypted The encrypted data
+ *
+ * @return {Promise<Object>} - Return object with decryptedData and
+ * status information
+ *
+ * Use Promise.catch to handle failed decryption.
+ * retObj.errorMsg will be an error message in this case.
+ */
+
+ async decryptAttachment(encrypted) {
+ EnigmailLog.DEBUG(`gnupg.js: decryptAttachment()\n`);
+
+ let args = EnigmailGpg.getStandardArgs(true);
+ args.push("--yes");
+ args = args.concat(EnigmailPassword.command());
+ args.push("-d");
+
+ let res = await EnigmailExecution.execAsync(EnigmailGpg.agentPath, args, encrypted);
+ return res;
+ }
+
+
+ /**
+ *
+ * @param {String} encrypted The encrypted data
+ * @param {Object} options Decryption options
+ *
+ * @return {Promise<Object>} - Return object with decryptedData and
+ * status information
+ *
+ * Use Promise.catch to handle failed decryption.
+ * retObj.errorMsg will be an error message in this case.
+ */
+
+ async decrypt(encrypted, options) {
+ EnigmailLog.DEBUG(`gnupg.js: decrypt()\n`);
+
+ options.logFile = EnigmailErrorHandling.getTempLogFile();
+ const args = GnuPGDecryption.getDecryptionArgs(options);
+ let res = await EnigmailExecution.execAsync(EnigmailGpg.agentPath, args, encrypted);
+ EnigmailErrorHandling.appendLogFileToDebug(options.logFile);
+
+ if (res.statusFlags & EnigmailConstants.MISSING_PASSPHRASE) {
+ EnigmailLog.ERROR("decryption.jsm: decryptMessageStart: Error - no passphrase supplied\n");
+ throw {
+ errorMsg: EnigmailLocale.getString("noPassphrase")
+ };
+ }
+
+ const result = {
+ exitCode: res.exitCode,
+ decryptedData: res.stdoutData
+ };
+ GnuPGDecryption.decryptMessageEnd(res.stderrData, res.exitCode, res.stdoutData.length, options.verifyOnly, options.noOutput, options.uiFlags, result);
+
+ return result;
+ }
+
+ /**
+ *
+ * @param {String} encrypted The encrypted data
+ * @param {Object} options Decryption options
+ *
+ * @return {Promise<Object>} - Return object with decryptedData and
+ * status information
+ *
+ * Use Promise.catch to handle failed decryption.
+ * retObj.errorMsg will be an error message in this case.
+ */
+
+ async decryptMime(encrypted, options) {
+ EnigmailLog.DEBUG(`gnupg.js: decryptMime()\n`);
+
+ // write something to gpg such that the process doesn't get stuck
+ if (encrypted.length === 0) {
+ encrypted = "NO DATA\n";
+ }
+
+ options.noOutput = false;
+ options.verifyOnly = false;
+ options.uiFlags = EnigmailConstants.UI_PGP_MIME;
+
+ return this.decrypt(encrypted, options);
+ }
+
+ /**
+ *
+ * @param {String} signed The signed data
+ * @param {Object} options Decryption options
+ *
+ * @return {Promise<Object>} - Return object with decryptedData and
+ * status information
+ *
+ * Use Promise.catch to handle failed decryption.
+ * retObj.errorMsg will be an error message in this case.
+ */
+
+ async verifyMime(signed, options) {
+ EnigmailLog.DEBUG(`gnupg.js: verifyMime()\n`);
+
+ options.noOutput = true;
+ options.verifyOnly = true;
+ options.uiFlags = EnigmailConstants.UI_PGP_MIME;
+
+ return this.decrypt(signed, options);
+ }
+
+ async getKeyListFromKeyBlock(keyBlockStr) {
+ let res;
+ res = await getGpgKeyData(keyBlockStr);
+ return res;
+ }
+
+}
+
+function getGnuPGAPI() {
+ return new GnuPGCryptoAPI();
+}
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/cryptoAPI/interface.js
@@ -0,0 +1,263 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+/**
+ * CryptoAPI - abstract interface
+ */
+
+var inspector;
+
+class CryptoAPI {
+ constructor() {
+ this.api_name = "null";
+ }
+
+ get apiName() {
+ return this.api_name;
+ }
+
+ /**
+ * Synchronize a promise: wait synchonously until a promise has completed and return
+ * the value that the promise returned.
+ *
+ * @param {Promise} promise: the promise to wait for
+ *
+ * @return {Variant} whatever the promise returns
+ */
+ sync(promise) {
+ console.log("CryptoAPI.sync() starting");
+
+ if (!inspector) {
+ inspector = Cc["@mozilla.org/jsinspector;1"].createInstance(Ci.nsIJSInspector);
+ }
+
+ let res = null;
+ let p = promise.then(gotResult => {
+ console.log("CryptoAPI.sync() good result: " + gotResult);
+ res = gotResult;
+ inspector.exitNestedEventLoop();
+ }).catch(gotResult => {
+ console.log("CryptoAPI.sync() failed result: " + gotResult);
+ res = gotResult;
+ inspector.exitNestedEventLoop();
+ });
+
+ inspector.enterNestedEventLoop(0);
+
+ console.log("CryptoAPI.sync() leaving");
+ return res;
+ }
+
+ /**
+ * Obtain signatures for a given set of key IDs.
+ *
+ * @param {String} keyId: space-separated list of key IDs
+ * @param {Boolean} ignoreUnknownUid: if true, filter out unknown signer's UIDs
+ *
+ * @return {Promise<Array of Object>} - see extractSignatures()
+ */
+ async getKeySignatures(keyId, ignoreUnknownUid = false) {
+ return null;
+ }
+
+ /**
+ * Export the minimum key for the public key object:
+ * public key, user ID, newest encryption subkey
+ *
+ * @param {String} fpr : a single FPR
+ * @param {String} email: [optional] the email address of the desired user ID.
+ * If the desired user ID cannot be found or is not valid, use the primary UID instead
+ *
+ * @return {Promise<Object>}:
+ * - exitCode (0 = success)
+ * - errorMsg (if exitCode != 0)
+ * - keyData: BASE64-encded string of key data
+ */
+ async getMinimalPubKey(fpr, email) {
+ return {
+ exitCode: -1,
+ errorMsg: "",
+ keyData: ""
+ };
+ }
+
+ /**
+ * Get the list of all konwn keys (including their secret keys)
+ * @param {Array of String} onlyKeys: [optional] only load data for specified key IDs
+ *
+ * @return {Promise<Array of Object>}
+ */
+ async getKeys(onlyKeys = null) {
+ return [];
+ }
+
+ /**
+ * Get groups defined in gpg.conf in the same structure as KeyObject
+ * [synchronous]
+ *
+ * @return {Array of KeyObject} with type = "grp"
+ */
+ getGroups() {
+ return [];
+ }
+
+ /**
+ * Extract a photo ID from a key, store it as file and return the file object.
+ *
+ * @param {String} keyId: Key ID / fingerprint
+ * @param {Number} photoNumber: number of the photo on the key, starting with 0
+ *
+ * @return {nsIFile} object or null in case no data / error.
+ */
+ async getPhotoFile(keyId, photoNumber) {
+ return null;
+ }
+
+ /**
+ * Import key(s) from a file
+ *
+ * @param {nsIFile} inputFile: the file holding the keys
+ *
+ * @return {Object} or null in case no data / error:
+ * - {Number} exitCode: result code (0: OK)
+ * - {Array of String) importedKeys: imported fingerprints
+ * - {Number} importSum: total number of processed keys
+ * - {Number} importUnchanged: number of unchanged keys
+ */
+
+ async importKeyFromFile(inputFile) {
+ return null;
+ }
+
+ /**
+ * Export secret key(s) to a file
+ *
+ * @param {String} keyId Specification by fingerprint or keyID
+ * @param {Boolean} minimalKey if true, reduce key to minimum required
+ *
+ * @return {Object}:
+ * - {Number} exitCode: result code (0: OK)
+ * - {String} keyData: ASCII armored key data material
+ * - {String} errorMsg: error message in case exitCode !== 0
+ */
+
+ async extractSecretKey(keyId, minimalKey) {
+ return null;
+ }
+
+ /**
+ * Determine the file name from OpenPGP data.
+ *
+ * @param {byte} byteData The encrypted data
+ *
+ * @return {String} - the name of the attached file
+ */
+
+ async getFileName(byteData) {
+ return null;
+ }
+
+ /**
+ * Verify the detached signature of an attachment (or in other words,
+ * check the signature of a file, given the file and the signature).
+ *
+ * @param {Path} filePath The signed file
+ * @param {Path} sigPath The signature to verify
+ *
+ * @return {Promise<String>} - A message from the verification.
+ *
+ * Use Promise.catch to handle failed verifications.
+ * The message will be an error message in this case.
+ */
+
+ async verifyAttachment(filePath, sigPath) {
+ return null;
+ }
+
+ /**
+ * Decrypt an attachment.
+ *
+ * @param {Bytes} encrypted The encrypted data
+ *
+ * @return {Promise<Object>} - Return object with decryptedData and
+ * status information
+ *
+ * Use Promise.catch to handle failed decryption.
+ * retObj.errorMsg will be an error message in this case.
+ */
+
+ async decryptAttachment(encrypted) {
+ return null;
+ }
+
+ /**
+ * Generic function to decrypt and/or verify an OpenPGP message.
+ *
+ * @param {String} encrypted The encrypted data
+ * @param {Object} options Decryption options
+ *
+ * @return {Promise<Object>} - Return object with decryptedData and
+ * status information
+ *
+ * Use Promise.catch to handle failed decryption.
+ * retObj.errorMsg will be an error message in this case.
+ */
+
+ async decrypt(encrypted, options) {
+ return null;
+ }
+
+ /**
+ * Decrypt a PGP/MIME-encrypted message
+ *
+ * @param {String} encrypted The encrypted data
+ * @param {Object} options Decryption options
+ *
+ * @return {Promise<Object>} - Return object with decryptedData and
+ * status information
+ *
+ * Use Promise.catch to handle failed decryption.
+ * retObj.errorMsg will be an error message in this case.
+ */
+
+ async decryptMime(encrypted, options) {
+ return null;
+ }
+
+ /**
+ * Verify a PGP/MIME-signed message
+ *
+ * @param {String} signed The signed data
+ * @param {Object} options Decryption options
+ *
+ * @return {Promise<Object>} - Return object with decryptedData and
+ * status information
+ *
+ * Use Promise.catch to handle failed decryption.
+ * retObj.errorMsg will be an error message in this case.
+ */
+
+ async verifyMime(signed, options) {
+ return null;
+ }
+
+ /**
+ * Get details (key ID, UID) of the data contained in a OpenPGP key block
+ *
+ * @param {String} keyBlockStr String: the contents of one or more public keys
+ *
+ * @return {Promise<Array>}: array of objects with the following structure:
+ * - id (key ID)
+ * - fpr
+ * - name (the UID of the key)
+ */
+
+ async getKeyListFromKeyBlock(keyBlockStr) {
+ return null;
+ }
+}
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/data.jsm
@@ -0,0 +1,168 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailData"];
+
+const SCRIPTABLEUNICODECONVERTER_CONTRACTID = "@mozilla.org/intl/scriptableunicodeconverter";
+
+const HEX_TABLE = "0123456789abcdef";
+
+function converter(charset) {
+ let unicodeConv = Cc[SCRIPTABLEUNICODECONVERTER_CONTRACTID].getService(Ci.nsIScriptableUnicodeConverter);
+ unicodeConv.charset = charset || "utf-8";
+ return unicodeConv;
+}
+
+var EnigmailData = {
+ getUnicodeData: function(data) {
+ // convert output from subprocess to Unicode
+ var tmpStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
+ tmpStream.setData(data, data.length);
+ var inStream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream);
+ inStream.init(tmpStream);
+ return inStream.read(tmpStream.available());
+ },
+
+ extractMessageId: function(uri) {
+ var messageId = "";
+
+ var matches = uri.match(/^enigmail:message\/(.+)/);
+
+ if (matches && (matches.length > 1)) {
+ messageId = matches[1];
+ }
+
+ return messageId;
+ },
+
+ extractMimeMessageId: function(uri) {
+ var messageId = "";
+
+ var matches = uri.match(/^enigmail:mime-message\/(.+)/);
+
+ if (matches && (matches.length > 1)) {
+ messageId = matches[1];
+ }
+
+ return messageId;
+ },
+
+ decodeQuotedPrintable: function(str) {
+ return unescape(str.replace(/%/g, "=25").replace(new RegExp('=', 'g'), '%'));
+ },
+
+ decodeBase64: function(str) {
+ return atob(str.replace(/[\s\r\n]*/g, ""));
+ },
+
+ /***
+ * Encode a string in base64, with a max. line length of 72 characters
+ */
+ encodeBase64: function(str) {
+ return btoa(str).replace(/(.{72})/g, "$1\r\n");
+ },
+
+ convertToUnicode: function(text, charset) {
+ if (!text || (charset && (charset.toLowerCase() == "iso-8859-1"))) {
+ return text;
+ }
+
+ // Encode plaintext
+ try {
+ return converter(charset).ConvertToUnicode(text);
+ }
+ catch (ex) {
+ return text;
+ }
+ },
+
+ convertFromUnicode: function(text, charset) {
+ if (!text) {
+ return "";
+ }
+
+ try {
+ return converter(charset).ConvertFromUnicode(text);
+ }
+ catch (ex) {
+ return text;
+ }
+ },
+
+ convertGpgToUnicode: function(text) {
+ if (typeof(text) === "string") {
+ text = text.replace(/\\x3a/ig, "\\e3A");
+ var a = text.search(/\\x[0-9a-fA-F]{2}/);
+ while (a >= 0) {
+ var ch = unescape('%' + text.substr(a + 2, 2));
+ var r = new RegExp("\\" + text.substr(a, 4));
+ text = text.replace(r, ch);
+
+ a = text.search(/\\x[0-9a-fA-F]{2}/);
+ }
+
+ text = EnigmailData.convertToUnicode(text, "utf-8").replace(/\\e3A/g, ":");
+ }
+
+ return text;
+ },
+
+ pack: function(value, bytes) {
+ let str = '';
+ let mask = 0xff;
+ for (let j = 0; j < bytes; j++) {
+ str = String.fromCharCode((value & mask) >> j * 8) + str;
+ mask <<= 8;
+ }
+
+ return str;
+ },
+
+ unpack: function(str) {
+ let len = str.length;
+ let value = 0;
+
+ for (let j = 0; j < len; j++) {
+ value <<= 8;
+ value |= str.charCodeAt(j);
+ }
+
+ return value;
+ },
+
+ bytesToHex: function(str) {
+ let len = str.length;
+
+ let hex = '';
+ for (let j = 0; j < len; j++) {
+ let charCode = str.charCodeAt(j);
+ hex += HEX_TABLE.charAt((charCode & 0xf0) >> 4) +
+ HEX_TABLE.charAt((charCode & 0x0f));
+ }
+
+ return hex;
+ },
+
+ /**
+ * Convert an ArrayBuffer (or Uint8Array) object into a string
+ */
+ arrayBufferToString: function(buffer) {
+ const MAXLEN = 102400;
+
+ let uArr = new Uint8Array(buffer);
+ let ret = "";
+ let len = buffer.byteLength;
+
+ for (let j = 0; j < Math.floor(len / MAXLEN) + 1; j++) {
+ ret += String.fromCharCode.apply(null, uArr.subarray(j * MAXLEN, ((j + 1) * MAXLEN)));
+ }
+
+ return ret;
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/decryption.jsm
@@ -0,0 +1,431 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailDecryption"];
+
+const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs.jsm").EnigmailPrefs;
+const EnigmailArmor = ChromeUtils.import("chrome://openpgp/content/modules/armor.jsm").EnigmailArmor;
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
+const EnigmailDialog = ChromeUtils.import("chrome://openpgp/content/modules/dialog.jsm").EnigmailDialog;
+const EnigmailHttpProxy = ChromeUtils.import("chrome://openpgp/content/modules/httpProxy.jsm").EnigmailHttpProxy;
+const EnigmailGpgAgent = ChromeUtils.import("chrome://openpgp/content/modules/gpgAgent.jsm").EnigmailGpgAgent;
+const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
+const EnigmailGpg = ChromeUtils.import("chrome://openpgp/content/modules/gpg.jsm").EnigmailGpg;
+const EnigmailErrorHandling = ChromeUtils.import("chrome://openpgp/content/modules/errorHandling.jsm").EnigmailErrorHandling;
+const EnigmailKeyRing = ChromeUtils.import("chrome://openpgp/content/modules/keyRing.jsm").EnigmailKeyRing;
+const EnigmailKey = ChromeUtils.import("chrome://openpgp/content/modules/key.jsm").EnigmailKey;
+const EnigmailPassword = ChromeUtils.import("chrome://openpgp/content/modules/passwords.jsm").EnigmailPassword;
+const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
+const EnigmailFuncs = ChromeUtils.import("chrome://openpgp/content/modules/funcs.jsm").EnigmailFuncs;
+const EnigmailCryptoAPI = ChromeUtils.import("chrome://openpgp/content/modules/cryptoAPI.jsm").EnigmailCryptoAPI;
+
+const STATUS_ERROR = EnigmailConstants.BAD_SIGNATURE | EnigmailConstants.DECRYPTION_FAILED;
+const STATUS_DECRYPTION_OK = EnigmailConstants.DECRYPTION_OKAY;
+const STATUS_GOODSIG = EnigmailConstants.GOOD_SIGNATURE;
+
+const NS_WRONLY = 0x02;
+
+function statusObjectFrom(signatureObj, exitCodeObj, statusFlagsObj, keyIdObj, userIdObj, sigDetailsObj, errorMsgObj, blockSeparationObj, encToDetailsObj) {
+ return {
+ signature: signatureObj,
+ exitCode: exitCodeObj,
+ statusFlags: statusFlagsObj,
+ keyId: keyIdObj,
+ userId: userIdObj,
+ sigDetails: sigDetailsObj,
+ message: errorMsgObj,
+ blockSeparation: blockSeparationObj,
+ encToDetails: encToDetailsObj
+ };
+}
+
+function newStatusObject() {
+ return statusObjectFrom({
+ value: ""
+ }, {}, {}, {}, {}, {}, {}, {}, {});
+}
+
+var EnigmailDecryption = {
+ isReady: function(win) {
+ return (EnigmailCore.getService(win)) && (!EnigmailKeyRing.isGeneratingKey());
+ },
+
+ getFromAddr: function(win) {
+ var fromAddr;
+ if (win && win.gFolderDisplay && win.gFolderDisplay.selectedMessage) {
+ fromAddr = win.gFolderDisplay.selectedMessage.author;
+ try {
+ fromAddr = EnigmailFuncs.stripEmail(fromAddr);
+ if (fromAddr.search(/[a-zA-Z0-9]@.*[\(\)]/) >= 0) {
+ fromAddr = false;
+ }
+ }
+ catch (ex) {
+ fromAddr = false;
+ }
+ }
+ return fromAddr;
+ },
+
+ /**
+ * Decrypts a PGP ciphertext and returns the the plaintext
+ *
+ *in @parent a window object
+ *in @uiFlags see flag options in EnigmailConstants, UI_INTERACTIVE, UI_ALLOW_KEY_IMPORT
+ *in @cipherText a string containing a PGP Block
+ *out @signatureObj
+ *out @exitCodeObj contains the exit code
+ *out @statusFlagsObj see status flags in nslEnigmail.idl, GOOD_SIGNATURE, BAD_SIGNATURE
+ *out @keyIdObj holds the key id
+ *out @userIdObj holds the user id
+ *out @sigDetailsObj
+ *out @errorMsgObj error string
+ *out @blockSeparationObj
+ *out @encToDetailsObj returns in details, which keys the mesage was encrypted for (ENC_TO entries)
+ *
+ * @return string plaintext ("" if error)
+ *
+ */
+ decryptMessage: function(parent, uiFlags, cipherText,
+ signatureObj, exitCodeObj,
+ statusFlagsObj, keyIdObj, userIdObj, sigDetailsObj, errorMsgObj,
+ blockSeparationObj, encToDetailsObj) {
+ const esvc = EnigmailCore.getEnigmailService();
+
+ EnigmailLog.DEBUG("decryption.jsm: decryptMessage(" + cipherText.length + " bytes, " + uiFlags + ")\n");
+
+ if (!cipherText)
+ return "";
+
+ var interactive = uiFlags & EnigmailConstants.UI_INTERACTIVE;
+ var allowImport = uiFlags & EnigmailConstants.UI_ALLOW_KEY_IMPORT;
+ var unverifiedEncryptedOK = uiFlags & EnigmailConstants.UI_UNVERIFIED_ENC_OK;
+ var oldSignature = signatureObj.value;
+
+ EnigmailLog.DEBUG("decryption.jsm: decryptMessage: oldSignature=" + oldSignature + "\n");
+
+ signatureObj.value = "";
+ exitCodeObj.value = -1;
+ statusFlagsObj.value = 0;
+ keyIdObj.value = "";
+ userIdObj.value = "";
+ errorMsgObj.value = "";
+
+ var beginIndexObj = {};
+ var endIndexObj = {};
+ var indentStrObj = {};
+ var blockType = EnigmailArmor.locateArmoredBlock(cipherText, 0, "", beginIndexObj, endIndexObj, indentStrObj);
+ if (!blockType || blockType == "SIGNATURE") {
+ // return without displaying a message
+ return "";
+ }
+
+ var publicKey = (blockType == "PUBLIC KEY BLOCK");
+
+ var verifyOnly = (blockType == "SIGNED MESSAGE");
+
+ var pgpBlock = cipherText.substr(beginIndexObj.value,
+ endIndexObj.value - beginIndexObj.value + 1);
+
+ if (indentStrObj.value) {
+ var indentRegexp = new RegExp("^" + indentStrObj.value, "gm");
+ pgpBlock = pgpBlock.replace(indentRegexp, "");
+ if (indentStrObj.value.substr(-1) == " ") {
+ var indentRegexpStr = "^" + indentStrObj.value.replace(/ $/m, "$");
+ indentRegexp = new RegExp(indentRegexpStr, "gm");
+ pgpBlock = pgpBlock.replace(indentRegexp, "");
+ }
+ }
+
+ // HACK to better support messages from Outlook: if there are empty lines, drop them
+ if (pgpBlock.search(/MESSAGE-----\r?\n\r?\nVersion/) >= 0) {
+ EnigmailLog.DEBUG("decryption.jsm: decryptMessage: apply Outlook empty line workaround\n");
+ pgpBlock = pgpBlock.replace(/\r?\n\r?\n/g, "\n");
+ }
+
+ const head = "";
+ var tail = cipherText.substr(endIndexObj.value + 1,
+ cipherText.length - endIndexObj.value - 1);
+
+ if (publicKey) {
+ if (!allowImport) {
+ errorMsgObj.value = EnigmailLocale.getString("keyInMessageBody");
+ statusFlagsObj.value |= EnigmailConstants.DISPLAY_MESSAGE;
+ statusFlagsObj.value |= EnigmailConstants.INLINE_KEY;
+
+ return "";
+ }
+
+ // Import public key
+ exitCodeObj.value = EnigmailKeyRing.importKey(parent, true, pgpBlock, "",
+ errorMsgObj);
+ if (exitCodeObj.value === 0) {
+ statusFlagsObj.value |= EnigmailConstants.IMPORTED_KEY;
+ }
+ return "";
+ }
+
+ var newSignature = "";
+
+ if (verifyOnly) {
+ newSignature = EnigmailArmor.extractSignaturePart(pgpBlock, EnigmailConstants.SIGNATURE_ARMOR);
+ if (oldSignature && (newSignature != oldSignature)) {
+ EnigmailLog.ERROR("enigmail.js: Enigmail.decryptMessage: Error - signature mismatch " + newSignature + "\n");
+ errorMsgObj.value = EnigmailLocale.getString("sigMismatch");
+ statusFlagsObj.value |= EnigmailConstants.DISPLAY_MESSAGE;
+
+ return "";
+ }
+ }
+
+ if (!EnigmailCore.getService(parent)) {
+ EnigmailLog.ERROR("decryption.jsm: decryptMessage: not yet initialized\n");
+ errorMsgObj.value = EnigmailLocale.getString("notInit");
+ statusFlagsObj.value |= EnigmailConstants.DISPLAY_MESSAGE;
+ return "";
+ }
+
+ if (EnigmailKeyRing.isGeneratingKey()) {
+ errorMsgObj.value = EnigmailLocale.getString("notComplete");
+ statusFlagsObj.value |= EnigmailConstants.DISPLAY_MESSAGE;
+ return "";
+ }
+
+ // limit output to 100 times message size to avoid DoS attack
+ var maxOutput = pgpBlock.length * 100;
+ let keyserver = EnigmailPrefs.getPref("autoKeyRetrieve");
+ let options = {
+ keyserver: keyserver,
+ keyserverProxy: EnigmailHttpProxy.getHttpProxy(keyserver),
+ fromAddr: EnigmailDecryption.getFromAddr(parent),
+ verifyOnly: verifyOnly,
+ noOutput: false,
+ maxOutputLength: maxOutput,
+ uiFlags: uiFlags
+ };
+ const cApi = EnigmailCryptoAPI();
+ let result = cApi.sync(cApi.decrypt(pgpBlock, options));
+ EnigmailLog.DEBUG("decryption.jsm: decryptMessage: decryption finished\n");
+ if (! result) {
+ return "";
+ }
+
+ var plainText = EnigmailData.getUnicodeData(result.decryptedData);
+ exitCodeObj.value = result.exitCode;
+ statusFlagsObj.value = result.statusFlags;
+ errorMsgObj.value = result.errorMsg;
+
+ // do not return anything if gpg signales DECRYPTION_FAILED
+ // (which could be possible in case of MDC errors)
+ if ((uiFlags & EnigmailConstants.UI_IGNORE_MDC_ERROR) &&
+ (result.statusFlags & EnigmailConstants.MISSING_MDC)) {
+ EnigmailLog.DEBUG("decryption.jsm: decryptMessage: ignoring MDC error\n");
+ }
+ else if (result.statusFlags & EnigmailConstants.DECRYPTION_FAILED) {
+ plainText = "";
+ }
+
+ userIdObj.value = result.userId;
+ keyIdObj.value = result.keyId;
+ sigDetailsObj.value = result.sigDetails;
+ if (encToDetailsObj) {
+ encToDetailsObj.value = result.encToDetails;
+ }
+ blockSeparationObj.value = result.blockSeparation;
+
+ if (tail.search(/\S/) >= 0) {
+ statusFlagsObj.value |= EnigmailConstants.PARTIALLY_PGP;
+ }
+
+
+ if (exitCodeObj.value === 0) {
+ // Normal return
+
+ var doubleDashSeparator = false;
+ try {
+ doubleDashSeparator = EnigmailPrefs.getPrefBranch().getBoolPref("doubleDashSeparator");
+ }
+ catch (ex) {}
+
+ if (doubleDashSeparator && (plainText.search(/(\r|\n)-- +(\r|\n)/) < 0)) {
+ // Workaround for MsgCompose stripping trailing spaces from sig separator
+ plainText = plainText.replace(/(\r|\n)--(\r|\n)/, "$1-- $2");
+ }
+
+ statusFlagsObj.value |= EnigmailConstants.DISPLAY_MESSAGE;
+
+ if (verifyOnly && indentStrObj.value) {
+ plainText = plainText.replace(/^/gm, indentStrObj.value);
+ }
+
+ return EnigmailDecryption.inlineInnerVerification(parent, uiFlags, plainText,
+ statusObjectFrom(signatureObj, exitCodeObj, statusFlagsObj, keyIdObj, userIdObj,
+ sigDetailsObj, errorMsgObj, blockSeparationObj, encToDetailsObj));
+ }
+
+ var pubKeyId = keyIdObj.value;
+
+ if (statusFlagsObj.value & EnigmailConstants.BAD_SIGNATURE) {
+ if (verifyOnly && indentStrObj.value) {
+ // Probably replied message that could not be verified
+ errorMsgObj.value = EnigmailLocale.getString("unverifiedReply") + "\n\n" + errorMsgObj.value;
+ return "";
+ }
+
+ // Return bad signature (for checking later)
+ signatureObj.value = newSignature;
+
+ }
+ else if (pubKeyId &&
+ (statusFlagsObj.value & EnigmailConstants.UNVERIFIED_SIGNATURE)) {
+
+ var innerKeyBlock;
+ if (verifyOnly) {
+ // Search for indented public key block in signed message
+ var innerBlockType = EnigmailArmor.locateArmoredBlock(pgpBlock, 0, "- ", beginIndexObj, endIndexObj, indentStrObj);
+ if (innerBlockType == "PUBLIC KEY BLOCK") {
+
+ innerKeyBlock = pgpBlock.substr(beginIndexObj.value,
+ endIndexObj.value - beginIndexObj.value + 1);
+
+ innerKeyBlock = innerKeyBlock.replace(/- -----/g, "-----");
+
+ statusFlagsObj.value |= EnigmailConstants.INLINE_KEY;
+ EnigmailLog.DEBUG("decryption.jsm: decryptMessage: innerKeyBlock found\n");
+ }
+ }
+
+ if (allowImport) {
+
+ var importedKey = false;
+
+ if (innerKeyBlock) {
+ var importErrorMsgObj = {};
+ var exitStatus = EnigmailKeyRing.importKey(parent, true, innerKeyBlock,
+ pubKeyId, importErrorMsgObj);
+
+ importedKey = (exitStatus === 0);
+
+ if (exitStatus > 0) {
+ EnigmailDialog.alert(parent, EnigmailLocale.getString("cantImport") + importErrorMsgObj.value);
+ }
+ }
+
+ if (importedKey) {
+ // Recursive call; note that EnigmailConstants.UI_ALLOW_KEY_IMPORT is unset
+ // to break the recursion
+ var uiFlagsDeep = interactive ? EnigmailConstants.UI_INTERACTIVE : 0;
+ signatureObj.value = "";
+ return EnigmailDecryption.decryptMessage(parent, uiFlagsDeep, pgpBlock,
+ signatureObj, exitCodeObj, statusFlagsObj,
+ keyIdObj, userIdObj, sigDetailsObj, errorMsgObj);
+ }
+
+ }
+
+ if (plainText && !unverifiedEncryptedOK) {
+ // Append original PGP block to unverified message
+ plainText = "-----BEGIN PGP UNVERIFIED MESSAGE-----\r\n" + plainText +
+ "-----END PGP UNVERIFIED MESSAGE-----\r\n\r\n" + pgpBlock;
+ }
+
+ }
+
+ return verifyOnly ? "" : plainText;
+ },
+
+ inlineInnerVerification: function(parent, uiFlags, text, statusObject) {
+ EnigmailLog.DEBUG("decryption.jsm: inlineInnerVerification()\n");
+
+ if (text && text.indexOf("-----BEGIN PGP SIGNED MESSAGE-----") === 0) {
+ var status = newStatusObject();
+ var newText = EnigmailDecryption.decryptMessage(parent, uiFlags, text,
+ status.signature, status.exitCode, status.statusFlags, status.keyId, status.userId,
+ status.sigDetails, status.message, status.blockSeparation, status.encToDetails);
+ if (status.exitCode.value === 0) {
+ text = newText;
+ // merge status into status object:
+ statusObject.statusFlags.value = statusObject.statusFlags.value | status.statusFlags.value;
+ statusObject.keyId.value = status.keyId.value;
+ statusObject.userId.value = status.userId.value;
+ statusObject.sigDetails.value = status.sigDetails.value;
+ statusObject.message.value = status.message.value;
+ // we don't merge encToDetails
+ }
+ }
+
+ return text;
+ },
+
+ decryptAttachment: function(parent, outFile, displayName, byteData,
+ exitCodeObj, statusFlagsObj, errorMsgObj) {
+ const esvc = EnigmailCore.getEnigmailService();
+
+ EnigmailLog.DEBUG("decryption.jsm: decryptAttachment(parent=" + parent + ", outFileName=" + outFile.path + ")\n");
+
+ let attachmentHead = byteData.substr(0, 200);
+ if (attachmentHead.match(/-----BEGIN PGP \w{5,10} KEY BLOCK-----/)) {
+ // attachment appears to be a PGP key file
+
+ if (EnigmailDialog.confirmDlg(parent, EnigmailLocale.getString("attachmentPgpKey", [displayName]),
+ EnigmailLocale.getString("keyMan.button.import"), EnigmailLocale.getString("dlg.button.view"))) {
+
+ let preview = EnigmailKey.getKeyListFromKeyBlock(byteData, errorMsgObj);
+ exitCodeObj.keyList = preview;
+ let exitStatus = 0;
+
+ if (errorMsgObj.value === "") {
+ if (preview.length > 0) {
+ if (preview.length == 1) {
+ exitStatus = EnigmailDialog.confirmDlg(parent, EnigmailLocale.getString("doImportOne", [preview[0].name, preview[0].id]));
+ }
+ else {
+ exitStatus = EnigmailDialog.confirmDlg(parent,
+ EnigmailLocale.getString("doImportMultiple", [
+ preview.map(function(a) {
+ return "\t" + a.name + " (" + a.id + ")";
+ }).
+ join("\n")
+ ]));
+ }
+
+ if (exitStatus) {
+ exitCodeObj.value = EnigmailKeyRing.importKey(parent, false, byteData, "", errorMsgObj);
+ statusFlagsObj.value = EnigmailConstants.IMPORTED_KEY;
+ }
+ else {
+ exitCodeObj.value = 0;
+ statusFlagsObj.value = EnigmailConstants.DISPLAY_MESSAGE;
+ }
+ }
+ }
+ }
+ else {
+ exitCodeObj.value = 0;
+ statusFlagsObj.value = EnigmailConstants.DISPLAY_MESSAGE;
+ }
+ return true;
+ }
+
+ //var outFileName = EnigmailFiles.getEscapedFilename(EnigmailFiles.getFilePathReadonly(outFile.QueryInterface(Ci.nsIFile), NS_WRONLY));
+
+ const cApi = EnigmailCryptoAPI();
+ let result = cApi.sync(cApi.decryptAttachment(byteData));
+
+ exitCodeObj.value = result.exitCode;
+ statusFlagsObj.value = result.statusFlags;
+ if (result.stdoutData.length > 0) {
+ return EnigmailFiles.writeFileContents(outFile, result.stdoutData);
+ }
+
+ return false;
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/dialog.jsm
@@ -0,0 +1,448 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailDialog"];
+
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailWindows = ChromeUtils.import("chrome://openpgp/content/modules/windows.jsm").EnigmailWindows;
+const EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs.jsm").EnigmailPrefs;
+const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
+
+const BUTTON_POS_0 = 1;
+const BUTTON_POS_1 = 1 << 8;
+const BUTTON_POS_2 = 1 << 16;
+
+const gPromptSvc = Cc["@mozilla.org/embedcomp/prompt-service;1"].getService(Ci.nsIPromptService);
+
+const LOCAL_FILE_CONTRACTID = "@mozilla.org/file/local;1";
+
+var EnigmailDialog = {
+
+ /***
+ * Confirmation dialog with OK / Cancel buttons (both customizable)
+ *
+ * @win: nsIWindow - parent window to display modal dialog; can be null
+ * @mesg: String - message text
+ * @okLabel: String - OPTIONAL label for OK button
+ * @cancelLabel: String - OPTIONAL label for cancel button
+ *
+ * @return: Boolean - true: OK pressed / false: Cancel or ESC pressed
+ */
+ confirmDlg: function(win, mesg, okLabel, cancelLabel) {
+
+ let buttonPressed = EnigmailDialog.msgBox(win, {
+ msgtext: mesg,
+ button1: okLabel ? okLabel : EnigmailLocale.getString("dlg.button.ok"),
+ cancelButton: cancelLabel ? cancelLabel : EnigmailLocale.getString("dlg.button.cancel"),
+ iconType: EnigmailConstants.ICONTYPE_QUESTION,
+ dialogTitle: EnigmailLocale.getString("enigConfirm")
+ },
+ null);
+
+ return (buttonPressed === 0);
+ },
+
+ /**
+ * Displays an alert dialog.
+ *
+ * @win: nsIWindow - parent window to display modal dialog; can be null
+ * @mesg: String - message text
+ *
+ * no return value
+ */
+ alert: function(win, mesg) {
+ EnigmailDialog.msgBox(win, {
+ msgtext: mesg,
+ button1: EnigmailLocale.getString("dlg.button.close"),
+ iconType: EnigmailConstants.ICONTYPE_ALERT,
+ dialogTitle: EnigmailLocale.getString("enigAlert")
+ },
+ null);
+ },
+
+ /**
+ * Displays an information dialog.
+ *
+ * @win: nsIWindow - parent window to display modal dialog; can be null
+ * @mesg: String - message text
+ *
+ * no return value
+ */
+ info: function(win, mesg) {
+ EnigmailDialog.msgBox(win, {
+ msgtext: mesg,
+ button1: EnigmailLocale.getString("dlg.button.close"),
+ iconType: EnigmailConstants.ICONTYPE_INFO,
+ dialogTitle: EnigmailLocale.getString("enigInfo")
+ },
+ null);
+ },
+
+ /**
+ * Displays an alert dialog with 1-3 optional buttons.
+ *
+ * @win: nsIWindow - parent window to display modal dialog; can be null
+ * @mesg: String - message text
+ * @checkboxLabel: String - if not null, display checkbox with text; the
+ * checkbox state is returned in checkedObj.value
+ * @button-Labels: String - use "&" to indicate access key
+ * use "buttonType:label" or ":buttonType" to indicate special button types
+ * (buttonType is one of cancel, help, extra1, extra2)
+ * @checkedObj: Object - holding the checkbox value
+ *
+ * @return: 0-2: button Number pressed
+ * -1: ESC or close window button pressed
+ *
+ */
+ longAlert: function(win, mesg, checkboxLabel, okLabel, labelButton2, labelButton3, checkedObj) {
+ var result = {
+ value: -1,
+ checked: false
+ };
+
+ if (!win) {
+ win = EnigmailWindows.getBestParentWin();
+ }
+
+ win.openDialog("chrome://openpgp/content/ui/enigmailMsgBox.xul", "_blank",
+ "chrome,dialog,modal,centerscreen,resizable,titlebar", {
+ msgtext: mesg,
+ checkboxLabel: checkboxLabel,
+ iconType: EnigmailConstants.ICONTYPE_ALERT,
+ button1: okLabel,
+ button2: labelButton2,
+ button3: labelButton3
+ },
+ result);
+
+ if (checkboxLabel) {
+ checkedObj.value = result.checked;
+ }
+ return result.value;
+ },
+
+ /**
+ * Displays a message box with 1-3 optional buttons.
+ *
+ * @win: nsIWindow - parent window to display modal dialog; can be null
+ * @argsObj: Object:
+ * - msgtext: String - message text
+ * - dialogTitle: String - title of the dialog
+ * - checkboxLabel: String - if not null, display checkbox with text; the
+ * checkbox state is returned in checkedObj.value
+ * - iconType: Number - Icon type: 1=Message / 2=Question / 3=Alert / 4=Error
+ *
+ * - buttonX: String - Button label (button 1-3) [button1 = "accept" button]
+ * use "&" to indicate access key
+ * - cancelButton String - Label for cancel button
+ * use "buttonType:label" or ":buttonType" to indicate special button types
+ * (buttonType is one of cancel, help, extra1, extra2)
+ * if no button is provided, OK will be displayed
+ *
+ * @checkedObj: Object - holding the checkbox value
+ *
+ * @return: 0-2: button Number pressed
+ * -1: cancel button, ESC or close window button pressed
+ *
+ */
+ msgBox: function(win, argsObj, checkedObj) {
+ var result = {
+ value: -1,
+ checked: false
+ };
+
+ if (!win) {
+ win = EnigmailWindows.getBestParentWin();
+ }
+
+ win.openDialog("chrome://openpgp/content/ui/enigmailMsgBox.xul", "",
+ "chrome,dialog,modal,centerscreen,resizable", argsObj, result);
+
+ if (argsObj.checkboxLabel) {
+ checkedObj.value = result.checked;
+ }
+ return result.value;
+ },
+
+ /**
+ * Display a dialog with a message and a text entry field
+ *
+ * @win: nsIWindow - parent window to display modal dialog; can be null
+ * @mesg: String - message text
+ * @valueObj: Object - object to hold the entered text in .value
+ *
+ * @return: Boolean - true if OK was pressed / false otherwise
+ */
+ promptValue: function(win, mesg, valueObj) {
+ return gPromptSvc.prompt(win, EnigmailLocale.getString("enigPrompt"),
+ mesg, valueObj, "", {});
+ },
+
+ /**
+ * Display an alert message with an OK button and a checkbox to hide
+ * the message in the future.
+ * In case the checkbox was pressed in the past, the dialog is skipped
+ *
+ * @win: nsIWindow - the parent window to hold the modal dialog
+ * @mesg: String - the localized message to display
+ * @prefText: String - the name of the Enigmail preference to read/store the
+ * the future display status
+ */
+ alertPref: function(win, mesg, prefText) {
+ const display = true;
+ const dontDisplay = false;
+
+ let prefValue = EnigmailPrefs.getPref(prefText);
+ if (prefValue === display) {
+ let checkBoxObj = {
+ value: false
+ };
+
+ let buttonPressed = EnigmailDialog.msgBox(win, {
+ msgtext: mesg,
+ dialogTitle: EnigmailLocale.getString("enigInfo"),
+ iconType: EnigmailConstants.ICONTYPE_INFO,
+ checkboxLabel: EnigmailLocale.getString("dlgNoPrompt")
+ },
+ checkBoxObj);
+
+ if (checkBoxObj.value && buttonPressed === 0) {
+ EnigmailPrefs.setPref(prefText, dontDisplay);
+ }
+ }
+ },
+
+ /**
+ * Display an alert dialog together with the message "this dialog will be
+ * displayed |counter| more times".
+ * If |counter| is 0, the dialog is not displayed.
+ *
+ * @win: nsIWindow - the parent window to hold the modal dialog
+ * @countPrefName: String - the name of the Enigmail preference to read/store the
+ * the |counter| value
+ * @mesg: String - the localized message to display
+ *
+ */
+ alertCount: function(win, countPrefName, mesg) {
+ let alertCount = EnigmailPrefs.getPref(countPrefName);
+
+ if (alertCount <= 0)
+ return;
+
+ alertCount--;
+ EnigmailPrefs.setPref(countPrefName, alertCount);
+
+ if (alertCount > 0) {
+ mesg += EnigmailLocale.getString("repeatPrefix", [alertCount]) + " ";
+ mesg += (alertCount == 1) ? EnigmailLocale.getString("repeatSuffixSingular") : EnigmailLocale.getString("repeatSuffixPlural");
+ }
+ else {
+ mesg += EnigmailLocale.getString("noRepeat");
+ }
+
+ EnigmailDialog.alert(win, mesg);
+ },
+
+ /**
+ * Display a confirmation dialog with OK / Cancel buttons (both customizable) and
+ * a checkbox to remember the selected choice.
+ *
+ *
+ * @win: nsIWindow - parent window to display modal dialog; can be null
+ * @mesg: String - message text
+ * @prefText String - the name of the Enigmail preference to read/store the
+ * the future display status.
+ * the default action is chosen
+ * @okLabel: String - OPTIONAL label for OK button
+ * @cancelLabel: String - OPTIONAL label for cancel button
+ *
+ * @return: Boolean - true: 1 pressed / 0: Cancel pressed / -1: ESC pressed
+ *
+ * If the dialog is not displayed:
+ * - if @prefText is type Boolean: return 1
+ * - if @prefText is type Number: return the last choice of the user
+ */
+ confirmPref: function(win, mesg, prefText, okLabel, cancelLabel) {
+ const notSet = 0;
+ const yes = 1;
+ const no = 2;
+ const display = true;
+ const dontDisplay = false;
+
+ var prefValue = EnigmailPrefs.getPref(prefText);
+
+ if (typeof(prefValue) != "boolean") {
+ // number: remember user's choice
+ switch (prefValue) {
+ case notSet:
+ {
+ let checkBoxObj = {
+ value: false
+ };
+ let buttonPressed = EnigmailDialog.msgBox(win, {
+ msgtext: mesg,
+ button1: okLabel ? okLabel : EnigmailLocale.getString("dlg.button.ok"),
+ cancelButton: cancelLabel ? cancelLabel : EnigmailLocale.getString("dlg.button.cancel"),
+ checkboxLabel: EnigmailLocale.getString("dlgKeepSetting"),
+ iconType: EnigmailConstants.ICONTYPE_QUESTION,
+ dialogTitle: EnigmailLocale.getString("enigConfirm")
+ }, checkBoxObj);
+
+ if (checkBoxObj.value) {
+ EnigmailPrefs.setPref(prefText, (buttonPressed === 0 ? yes : no));
+ }
+ return (buttonPressed === 0 ? 1 : 0);
+ }
+ case yes:
+ return 1;
+ case no:
+ return 0;
+ default:
+ return -1;
+ }
+ }
+ else {
+ // boolean: "do not show this dialog anymore" (and return default)
+ switch (prefValue) {
+ case display:
+ {
+ let checkBoxObj = {
+ value: false
+ };
+ let buttonPressed = EnigmailDialog.msgBox(win, {
+ msgtext: mesg,
+ button1: okLabel ? okLabel : EnigmailLocale.getString("dlg.button.ok"),
+ cancelButton: cancelLabel ? cancelLabel : EnigmailLocale.getString("dlg.button.cancel"),
+ checkboxLabel: EnigmailLocale.getString("dlgNoPrompt"),
+ iconType: EnigmailConstants.ICONTYPE_QUESTION,
+ dialogTitle: EnigmailLocale.getString("enigConfirm")
+ }, checkBoxObj);
+
+ if (checkBoxObj.value) {
+ EnigmailPrefs.setPref(prefText, false);
+ }
+ return (buttonPressed === 0 ? 1 : 0);
+ }
+ case dontDisplay:
+ return 1;
+ default:
+ return -1;
+ }
+ }
+ },
+
+ /**
+ * Display a "open file" or "save file" dialog
+ *
+ * win: nsIWindow - parent window
+ * title: String - window title
+ * displayDir: String - optional: directory to be displayed
+ * save: Boolean - true = Save file / false = Open file
+ * defaultExtension: String - optional: extension for the type of files to work with, e.g. "asc"
+ * defaultName: String - optional: filename, incl. extension, that should be suggested to
+ * the user as default, e.g. "keys.asc"
+ * filterPairs: Array - optional: [title, extension], e.g. ["Pictures", "*.jpg; *.png"]
+ *
+ * return value: nsIFile object representing the file to load or save
+ */
+ filePicker: function(win, title, displayDir, save, defaultExtension, defaultName, filterPairs) {
+ EnigmailLog.DEBUG("enigmailCommon.jsm: filePicker: " + save + "\n");
+
+ let filePicker = Cc["@mozilla.org/filepicker;1"].createInstance();
+ filePicker = filePicker.QueryInterface(Ci.nsIFilePicker);
+
+ let mode = save ? Ci.nsIFilePicker.modeSave : Ci.nsIFilePicker.modeOpen;
+
+ filePicker.init(win, title, mode);
+
+ if (displayDir) {
+ var localFile = Cc[LOCAL_FILE_CONTRACTID].createInstance(Ci.nsIFile);
+
+ try {
+ localFile.initWithPath(displayDir);
+ filePicker.displayDirectory = localFile;
+ }
+ catch (ex) {}
+ }
+
+ if (defaultExtension) {
+ filePicker.defaultExtension = defaultExtension;
+ }
+
+ if (defaultName) {
+ filePicker.defaultString = defaultName;
+ }
+
+ let nfilters = 0;
+ if (filterPairs && filterPairs.length) {
+ nfilters = filterPairs.length / 2;
+ }
+
+ for (let index = 0; index < nfilters; index++) {
+ filePicker.appendFilter(filterPairs[2 * index], filterPairs[2 * index + 1]);
+ }
+
+ filePicker.appendFilters(Ci.nsIFilePicker.filterAll);
+
+ let inspector = Cc["@mozilla.org/jsinspector;1"].createInstance(Ci.nsIJSInspector);
+ let gotFile = null;
+ filePicker.open(res => {
+ if (res != Ci.nsIFilePicker.returnOK && res != Ci.nsIFilePicker.returnReplace) {
+ inspector.exitNestedEventLoop();
+ return;
+ }
+
+ gotFile = filePicker.file.QueryInterface(Ci.nsIFile);
+ inspector.exitNestedEventLoop();
+ });
+
+ inspector.enterNestedEventLoop(0); // wait for async process to terminate
+
+ return gotFile;
+ },
+
+ /**
+ * Displays a dialog with success/failure information after importing
+ * keys.
+ *
+ * @param win: nsIWindow - parent window to display modal dialog; can be null
+ * @param keyList: Array of String - imported keyIDs
+ *
+ * @return: 0-2: button Number pressed
+ * -1: ESC or close window button pressed
+ *
+ */
+ keyImportDlg: function(win, keyList) {
+ var result = {
+ value: -1,
+ checked: false
+ };
+
+ if (!win) {
+ win = EnigmailWindows.getBestParentWin();
+ }
+
+ win.openDialog("chrome://openpgp/content/ui/enigmailKeyImportInfo.xul", "",
+ "chrome,dialog,modal,centerscreen,resizable", {
+ keyList: keyList
+ },
+ result);
+
+ return result.value;
+ },
+ /**
+ * return a pre-initialized prompt service
+ */
+ getPromptSvc: function() {
+ return gPromptSvc;
+ }
+};
+
+EnigmailWindows.alert = EnigmailDialog.alert;
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/dns.jsm
@@ -0,0 +1,345 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * This module provides DNS query functionality via subprocesses.
+ * Supported record types: MX, SRV
+ *
+ * The following tools are currently supported:
+ * Windows: nslookup
+ * Unix/Linux: dig, kdig, host, nslookup
+ */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailDns"];
+
+const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailOS = ChromeUtils.import("chrome://openpgp/content/modules/os.jsm").EnigmailOS;
+const subprocess = ChromeUtils.import("chrome://openpgp/content/modules/subprocess.jsm").subprocess;
+const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
+
+const RESTYPE_WIN_NLSOOKUP = 1;
+const RESTYPE_UNIX_NLSOOKUP = 2;
+const RESTYPE_DIG = 3;
+const RESTYPE_HOST = 4;
+const RESTYPE_NOT_AVAILABLE = 99;
+
+var gHandler = null,
+ gResolverExecutable = null;
+
+var EnigmailDns = {
+ /**
+ * Perform a DNS lookup
+ *
+ * @param {String} recordType: The resource record type to query. MX and SRV are currently supported.
+ * @param {String} queryName: The name to search for, e.g. "enigmail.net"
+ *
+ * @return {Promise<Array{String}>}: array of server(s) handling
+ *
+ */
+ lookup: async function(recordType, queryName) {
+ EnigmailLog.DEBUG(`dns.jsm: lookup(${recordType}, ${queryName})\n`);
+ if (!determineResolver()) return null;
+
+ switch (recordType.toUpperCase()) {
+ case "MX":
+ case "SRV":
+ break;
+ default:
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ }
+ let dnsHandler = new gHandler(gResolverExecutable);
+ return dnsHandler.execute(recordType, queryName);
+ }
+};
+
+/**
+ * Determine the DNS resolver tool to use (e.g. dig, nslookup)
+ *
+ * @return {Boolean}: true: tool found / false: no tool found
+ */
+
+function determineResolver() {
+ if (!gHandler) {
+ gHandler = GenericHandler;
+ if (EnigmailOS.isWin32) {
+ gResolverExecutable = EnigmailFiles.resolvePathWithEnv("nslookup");
+ if (gResolverExecutable) {
+ EnigmailLog.DEBUG(`dns.jsm: determineResolver: executable = ${gResolverExecutable.path}\n`);
+ gHandler = NsLookupHandler_Windows;
+ }
+ }
+ else {
+ determineLinuxResolver();
+ }
+
+ if (!gResolverExecutable) EnigmailLog.DEBUG(`dns.jsm: determineResolver: no executable found\n`);
+
+ }
+
+ return gHandler !== GenericHandler;
+}
+
+
+function determineLinuxResolver() {
+ EnigmailLog.DEBUG(`dns.jsm: determineLinuxResolver()\n`);
+ const services = [{
+ exe: "dig",
+ class: DigHandler
+ }, {
+ exe: "kdig",
+ class: DigHandler
+ }, {
+ exe: "host",
+ class: HostHandler
+ }, {
+ exe: "nslookup",
+ class: NsLookupHandler
+ }];
+
+ for (let i of services) {
+ gResolverExecutable = EnigmailFiles.resolvePathWithEnv(i.exe);
+ if (gResolverExecutable) {
+ EnigmailLog.DEBUG(`dns.jsm: determineLinuxResolver: found ${i.class.handlerType}\n`);
+
+ gHandler = i.class;
+ return;
+ }
+ }
+}
+
+/**
+ * Base class for executing DNS requests
+ */
+class GenericHandler {
+ constructor(handlerFile) {
+ this._handlerFile = handlerFile;
+ this.recordType = "";
+ this.hostName = "";
+ }
+
+ getCmdArgs() {
+ return [];
+ }
+
+ execute(recordType, hostName) {
+ return new Promise((resolve, reject) => {
+
+ this.recordType = recordType.toUpperCase();
+ this.hostName = hostName;
+ let args = this.getCmdArgs();
+
+ if (args.length === 0) {
+ resolve([]);
+ return;
+ }
+
+ let stdoutData = "",
+ stderrData = "";
+ let self = this;
+
+ EnigmailLog.DEBUG(`dns.jsm: execute(): launching ${EnigmailFiles.formatCmdLine(this._handlerFile, args)}\n`);
+
+ subprocess.call({
+ command: this._handlerFile,
+ arguments: args,
+ environment: EnigmailCore.getEnvList(),
+ charset: null,
+ stdout: function(data) {
+ //EnigmailLog.DEBUG(`dns.jsm: execute.stdout: got data ${data}\n`);
+ stdoutData += data;
+ },
+ stderr: function(data) {
+ //EnigmailLog.DEBUG(`dns.jsm: execute.stderr: got data ${data}\n`);
+ stderrData += data;
+ },
+ done: function(result) {
+ EnigmailLog.DEBUG(`dns.jsm: execute.done(${result.exitCode})\n`);
+ try {
+ if (result.exitCode === 0) {
+ resolve(self.parseResult(stdoutData));
+ }
+ else {
+ resolve([]);
+ }
+ }
+ catch (ex) {
+ reject(ex);
+ }
+ },
+ mergeStderr: false
+ });
+ });
+ }
+
+ parseResult() {
+ return [];
+ }
+}
+
+/**
+ * Handler class for "dig" and "kdig"
+ */
+class DigHandler extends GenericHandler {
+ constructor(handlerFile) {
+ super(handlerFile);
+ this.handlerType = "dig";
+ }
+
+ getCmdArgs() {
+ return ["-t", this.recordType, "+short", this.hostName];
+ }
+
+ parseResult(stdoutData) {
+ let hosts = [];
+ let lines = stdoutData.split(/[\r\n]+/);
+
+ if (this.recordType === "MX") {
+ for (let i = 0; i < lines.length; i++) {
+ let m = lines[i].match(/^(\d+ )(.*)\./);
+
+ if (m && m.length >= 3) hosts.push(m[2]);
+ }
+ }
+ else if (this.recordType === "SRV") {
+ for (let i = 0; i < lines.length; i++) {
+ let m = lines[i].match(/^(\d+) (\d+) (\d+) ([^ ]+)\.$/);
+
+ if (m && m.length >= 5) hosts.push(m[4] + ":" + m[3]);
+ }
+ }
+
+ return hosts;
+ }
+}
+
+/**
+ * Handler class for "host"
+ */
+
+class HostHandler extends GenericHandler {
+ constructor(handlerFile) {
+ super(handlerFile);
+ this.handlerType = "host";
+ }
+
+ getCmdArgs() {
+ return ["-t", this.recordType, this.hostName];
+ }
+
+ parseResult(stdoutData) {
+ if (stdoutData.search(/3\(NXDOMAIN\)/) >= 0) return [];
+
+ let hosts = [];
+ let lines = stdoutData.split(/[\r\n]+/);
+
+ if (this.recordType === "MX") {
+ for (let i = 0; i < lines.length; i++) {
+ let m = lines[i].match(/^(.* )([^ ]+)\.$/);
+
+ if (m && m.length >= 3) hosts.push(m[2]);
+ }
+ }
+ else if (this.recordType === "SRV") {
+ for (let i = 0; i < lines.length; i++) {
+ let m = lines[i].match(/^(.*) (\d+) ([^ ]+)\.$/);
+
+ if (m && m.length >= 4) hosts.push(m[3] + ":" + m[2]);
+ }
+ }
+
+ return hosts;
+ }
+}
+
+/**
+ * Handler class for "nslookup" (on Linux/Unix)
+ */
+
+class NsLookupHandler extends GenericHandler {
+ constructor(handlerFile) {
+ super(handlerFile);
+ this.handlerType = "nslookup";
+ }
+
+ getCmdArgs() {
+ return ["-type=" + this.recordType, this.hostName];
+ }
+
+ parseResult(stdoutData) {
+ let hosts = [];
+ let lines = stdoutData.split(/[\r\n]+/);
+
+ if (lines.length > 3 && lines[3].search(/: NXDOMAIN/) > 0) return [];
+
+ if (this.recordType === "MX") {
+ let reg = new RegExp("^" + this.hostName.toLowerCase() + "(.* )([^ \t]+.*[^\.])\\.?$");
+ for (let i = 2; i < lines.length; i++) {
+ let m = lines[i].match(reg);
+
+ if (m && m.length >= 3) hosts.push(m[2]);
+ if (lines[i].length < 5) break;
+ }
+ }
+ else if (this.recordType === "SRV") {
+ for (let i = 2; i < lines.length; i++) {
+ let m = lines[i].match(/^(.*) (\d+) ([^ ]+)\.$/);
+
+ if (m && m.length >= 3) hosts.push(m[3] + ":" + m[2]);
+ if (lines[i].length < 5) break;
+ }
+ }
+
+ return hosts;
+ }
+}
+
+/**
+ * Handler class for "nslookup" on Windows
+ */
+
+class NsLookupHandler_Windows extends NsLookupHandler {
+
+ parseResult(stdoutData) {
+ let hosts = [];
+ let lines = stdoutData.split(/[\r\n]+/);
+
+ if (this.recordType === "MX") {
+ let reg = new RegExp("^" + this.hostName.toLowerCase() + "(.* )([^ \t]+.*[^\.])\\.?$");
+ for (let i = 2; i < lines.length; i++) {
+ let m = lines[i].match(reg);
+
+ if (m && m.length >= 3) hosts.push(m[2]);
+ if (lines[i].length < 5) break;
+ }
+ }
+ else if (this.recordType === "SRV") {
+ let svc = null;
+ for (let i = 2; i < lines.length; i++) {
+ if (lines[i].search(/SRV service location:$/) > 0) {
+ svc = null;
+ continue;
+ }
+
+ let m = lines[i].match(/^[\t ]+(port|svr hostname)([\t =]+)([^ \t]+)$/);
+
+ if (m && m.length >= 4) {
+ if (m[1] === "port" && svc === null) {
+ svc = m[3];
+ }
+ else if (m[1] === "svr hostname" && svc) {
+ hosts.push(m[3] + ":" + svc);
+ }
+ }
+ if (lines[i].length < 5) break;
+ }
+ }
+
+ return hosts;
+ }
+}
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/encryption.jsm
@@ -0,0 +1,499 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailEncryption"];
+
+const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
+const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs.jsm").EnigmailPrefs;
+const EnigmailApp = ChromeUtils.import("chrome://openpgp/content/modules/app.jsm").EnigmailApp;
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+const EnigmailDialog = ChromeUtils.import("chrome://openpgp/content/modules/dialog.jsm").EnigmailDialog;
+const EnigmailGpgAgent = ChromeUtils.import("chrome://openpgp/content/modules/gpgAgent.jsm").EnigmailGpgAgent;
+const EnigmailGpg = ChromeUtils.import("chrome://openpgp/content/modules/gpg.jsm").EnigmailGpg;
+const EnigmailErrorHandling = ChromeUtils.import("chrome://openpgp/content/modules/errorHandling.jsm").EnigmailErrorHandling;
+const EnigmailExecution = ChromeUtils.import("chrome://openpgp/content/modules/execution.jsm").EnigmailExecution;
+const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
+const EnigmailPassword = ChromeUtils.import("chrome://openpgp/content/modules/passwords.jsm").EnigmailPassword;
+const EnigmailFuncs = ChromeUtils.import("chrome://openpgp/content/modules/funcs.jsm").EnigmailFuncs;
+const EnigmailKeyRing = ChromeUtils.import("chrome://openpgp/content/modules/keyRing.jsm").EnigmailKeyRing;
+const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
+
+
+const gMimeHashAlgorithms = [null, "sha1", "ripemd160", "sha256", "sha384", "sha512", "sha224", "md5"];
+
+const ENC_TYPE_MSG = 0;
+const ENC_TYPE_ATTACH_BINARY = 1;
+const ENC_TYPE_ATTACH_ASCII = 2;
+
+const GPG_COMMENT_OPT = "Using GnuPG with %s - https://doesnotexist-openpgp-integration.thunderbird/";
+
+
+var EnigmailEncryption = {
+ getEncryptCommand: function(fromMailAddr, toMailAddr, bccMailAddr, hashAlgorithm, sendFlags, isAscii, errorMsgObj,
+ logFileObj) {
+ EnigmailLog.DEBUG("encryption.jsm: getEncryptCommand: hashAlgorithm=" + hashAlgorithm + "\n");
+
+ try {
+ fromMailAddr = EnigmailFuncs.stripEmail(fromMailAddr);
+ toMailAddr = EnigmailFuncs.stripEmail(toMailAddr);
+ bccMailAddr = EnigmailFuncs.stripEmail(bccMailAddr);
+
+ }
+ catch (ex) {
+ errorMsgObj.value = EnigmailLocale.getString("invalidEmail");
+ return null;
+ }
+
+ var defaultSend = sendFlags & EnigmailConstants.SEND_DEFAULT;
+ var signMsg = sendFlags & EnigmailConstants.SEND_SIGNED;
+ var encryptMsg = sendFlags & EnigmailConstants.SEND_ENCRYPTED;
+ var usePgpMime = sendFlags & EnigmailConstants.SEND_PGP_MIME;
+
+ var useDefaultComment = false;
+ try {
+ useDefaultComment = EnigmailPrefs.getPref("useDefaultComment");
+ }
+ catch (ex) {}
+
+ var hushMailSupport = false;
+ try {
+ hushMailSupport = EnigmailPrefs.getPref("hushMailSupport");
+ }
+ catch (ex) {}
+
+ var detachedSig = (usePgpMime || (sendFlags & EnigmailConstants.SEND_ATTACHMENT)) && signMsg && !encryptMsg;
+
+ var toAddrList = toMailAddr.split(/\s*,\s*/);
+ var bccAddrList = bccMailAddr.split(/\s*,\s*/);
+ var k;
+
+ var encryptArgs = EnigmailGpg.getStandardArgs(true);
+
+ if (!useDefaultComment)
+ encryptArgs = encryptArgs.concat(["--comment", GPG_COMMENT_OPT.replace(/%s/, EnigmailApp.getName())]);
+
+ var angledFromMailAddr = ((fromMailAddr.search(/^0x/) === 0) || hushMailSupport) ?
+ fromMailAddr : "<" + fromMailAddr + ">";
+ angledFromMailAddr = angledFromMailAddr.replace(/(["'`])/g, "\\$1");
+
+ if (signMsg && hashAlgorithm) {
+ encryptArgs = encryptArgs.concat(["--digest-algo", hashAlgorithm]);
+ }
+
+ if (logFileObj) {
+ logFileObj.value = EnigmailErrorHandling.getTempLogFile();
+ encryptArgs.push("--log-file");
+ encryptArgs.push(EnigmailFiles.getEscapedFilename(EnigmailFiles.getFilePath(logFileObj.value)));
+ }
+
+ if (encryptMsg) {
+ switch (isAscii) {
+ case ENC_TYPE_MSG:
+ encryptArgs.push("-a");
+ encryptArgs.push("-t");
+ break;
+ case ENC_TYPE_ATTACH_ASCII:
+ encryptArgs.push("-a");
+ }
+
+ encryptArgs.push("--encrypt");
+
+ if (signMsg)
+ encryptArgs.push("--sign");
+
+ if (sendFlags & EnigmailConstants.SEND_ALWAYS_TRUST) {
+ encryptArgs.push("--trust-model");
+ encryptArgs.push("always");
+ }
+ if ((sendFlags & EnigmailConstants.SEND_ENCRYPT_TO_SELF) && fromMailAddr)
+ encryptArgs = encryptArgs.concat(["--encrypt-to", angledFromMailAddr]);
+
+ for (k = 0; k < toAddrList.length; k++) {
+ toAddrList[k] = toAddrList[k].replace(/'/g, "\\'");
+ if (toAddrList[k].length > 0) {
+ encryptArgs.push("-r");
+ if (toAddrList[k].search(/^GROUP:/) === 0) {
+ // groups from gpg.conf file
+ encryptArgs.push(toAddrList[k].substr(6));
+ }
+ else {
+ encryptArgs.push((hushMailSupport || (toAddrList[k].search(/^0x/) === 0)) ? toAddrList[k] : "<" + toAddrList[k] + ">");
+ }
+ }
+ }
+
+ for (k = 0; k < bccAddrList.length; k++) {
+ bccAddrList[k] = bccAddrList[k].replace(/'/g, "\\'");
+ if (bccAddrList[k].length > 0) {
+ encryptArgs.push("--hidden-recipient");
+ encryptArgs.push((hushMailSupport || (bccAddrList[k].search(/^0x/) === 0)) ? bccAddrList[k] : "<" + bccAddrList[k] + ">");
+ }
+ }
+
+ }
+ else if (detachedSig) {
+ encryptArgs = encryptArgs.concat(["-s", "-b"]);
+
+ switch (isAscii) {
+ case ENC_TYPE_MSG:
+ encryptArgs = encryptArgs.concat(["-a", "-t"]);
+ break;
+ case ENC_TYPE_ATTACH_ASCII:
+ encryptArgs.push("-a");
+ }
+
+ }
+ else if (signMsg) {
+ encryptArgs = encryptArgs.concat(["-t", "--clearsign"]);
+ }
+
+ if (fromMailAddr) {
+ encryptArgs = encryptArgs.concat(["-u", angledFromMailAddr]);
+ }
+
+ return encryptArgs;
+ },
+
+ /**
+ * Determine if the sender key ID or user ID can be used for signing and/or encryption
+ *
+ * @param sendFlags: Number - the send Flags; need to contain SEND_SIGNED and/or SEND_ENCRYPTED
+ * @param fromMailAddr: String - the sender email address or key ID
+ *
+ * @return Object:
+ * - keyId: String - the found key ID, or null if fromMailAddr is not valid
+ * - errorMsg: String - the erorr message if key not valid, or null if key is valid
+ */
+ determineOwnKeyUsability: function(sendFlags, fromMailAddr) {
+ EnigmailLog.DEBUG("encryption.jsm: determineOwnKeyUsability: sendFlags=" + sendFlags + ", sender=" + fromMailAddr + "\n");
+
+ let keyList = [];
+ let ret = {
+ keyId: null,
+ errorMsg: null
+ };
+
+ let sign = (sendFlags & EnigmailConstants.SEND_SIGNED ? true : false);
+ let encrypt = (sendFlags & EnigmailConstants.SEND_ENCRYPTED ? true : false);
+
+ if (fromMailAddr.search(/^(0x)?[A-Z0-9]+$/) === 0) {
+ // key ID specified
+ let key = EnigmailKeyRing.getKeyById(fromMailAddr);
+ keyList.push(key);
+ }
+ else {
+ // email address specified
+ keyList = EnigmailKeyRing.getKeysByUserId(fromMailAddr);
+ }
+
+ if (keyList.length === 0) {
+ ret.errorMsg = EnigmailLocale.getString("errorOwnKeyUnusable", fromMailAddr);
+ return ret;
+ }
+
+ if (sign) {
+ keyList = keyList.reduce(function _f(p, keyObj) {
+ if (keyObj && keyObj.getSigningValidity().keyValid) p.push(keyObj);
+ return p;
+ }, []);
+ }
+
+ if (encrypt) {
+ keyList = keyList.reduce(function _f(p, keyObj) {
+ if (keyObj && keyObj.getEncryptionValidity().keyValid) p.push(keyObj);
+ return p;
+ }, []);
+ }
+
+ if (keyList.length === 0) {
+ if (sign) {
+ ret.errorMsg = EnigmailErrorHandling.determineInvSignReason(fromMailAddr);
+ }
+ else {
+ ret.errorMsg = EnigmailErrorHandling.determineInvRcptReason(fromMailAddr);
+ }
+ }
+ else {
+ ret.keyId = keyList[0].fpr;
+ }
+
+ return ret;
+ },
+
+ encryptMessageStart: function(win, uiFlags, fromMailAddr, toMailAddr, bccMailAddr,
+ hashAlgorithm, sendFlags, listener, statusFlagsObj, errorMsgObj) {
+ EnigmailLog.DEBUG("encryption.jsm: encryptMessageStart: uiFlags=" + uiFlags + ", from " + fromMailAddr + " to " + toMailAddr + ", hashAlgorithm=" + hashAlgorithm + " (" + EnigmailData.bytesToHex(
+ EnigmailData.pack(sendFlags, 4)) + ")\n");
+
+ let keyUseability = this.determineOwnKeyUsability(sendFlags, fromMailAddr);
+
+ if (!keyUseability.keyId) {
+ EnigmailLog.DEBUG("encryption.jsm: encryptMessageStart: own key invalid\n");
+ errorMsgObj.value = keyUseability.errorMsg;
+ statusFlagsObj.value = EnigmailConstants.INVALID_RECIPIENT | EnigmailConstants.NO_SECKEY | EnigmailConstants.DISPLAY_MESSAGE;
+
+ return null;
+ }
+
+ var pgpMime = uiFlags & EnigmailConstants.UI_PGP_MIME;
+
+ var hashAlgo = gMimeHashAlgorithms[EnigmailPrefs.getPref("mimeHashAlgorithm")];
+
+ if (hashAlgorithm) {
+ hashAlgo = hashAlgorithm;
+ }
+
+ errorMsgObj.value = "";
+
+ if (!sendFlags) {
+ EnigmailLog.DEBUG("encryption.jsm: encryptMessageStart: NO ENCRYPTION!\n");
+ errorMsgObj.value = EnigmailLocale.getString("notRequired");
+ return null;
+ }
+
+ if (!EnigmailCore.getService(win)) {
+ EnigmailLog.ERROR("encryption.jsm: encryptMessageStart: not yet initialized\n");
+ errorMsgObj.value = EnigmailLocale.getString("notInit");
+ return null;
+ }
+
+ let logFileObj = {};
+ let encryptArgs = EnigmailEncryption.getEncryptCommand(fromMailAddr, toMailAddr, bccMailAddr, hashAlgo, sendFlags, ENC_TYPE_MSG, errorMsgObj, logFileObj);
+ if (!encryptArgs)
+ return null;
+
+ var signMsg = sendFlags & EnigmailConstants.SEND_SIGNED;
+ if (!listener) {
+ listener = {};
+ }
+ if ("done" in listener) {
+ listener.outerDone = listener.done;
+ }
+
+ listener.done = function(exitCode) {
+ EnigmailErrorHandling.appendLogFileToDebug(logFileObj.value);
+ if (this.outerDone) {
+ this.outerDone(exitCode);
+ }
+ };
+
+ var proc = EnigmailExecution.execStart(EnigmailGpgAgent.agentPath, encryptArgs, signMsg, win, listener, statusFlagsObj);
+
+ if (statusFlagsObj.value & EnigmailConstants.MISSING_PASSPHRASE) {
+ EnigmailLog.ERROR("encryption.jsm: encryptMessageStart: Error - no passphrase supplied\n");
+
+ errorMsgObj.value = "";
+ }
+
+ if (pgpMime && errorMsgObj.value) {
+ EnigmailDialog.alert(win, errorMsgObj.value);
+ }
+
+ return proc;
+ },
+
+ encryptMessageEnd: function(fromMailAddr, stderrStr, exitCode, uiFlags, sendFlags, outputLen, retStatusObj) {
+ EnigmailLog.DEBUG("encryption.jsm: encryptMessageEnd: uiFlags=" + uiFlags + ", sendFlags=" + EnigmailData.bytesToHex(EnigmailData.pack(sendFlags, 4)) + ", outputLen=" + outputLen + "\n");
+
+ var pgpMime = uiFlags & EnigmailConstants.UI_PGP_MIME;
+ var defaultSend = sendFlags & EnigmailConstants.SEND_DEFAULT;
+ var signMsg = sendFlags & EnigmailConstants.SEND_SIGNED;
+ var encryptMsg = sendFlags & EnigmailConstants.SEND_ENCRYPTED;
+
+ retStatusObj.statusFlags = 0;
+ retStatusObj.errorMsg = "";
+ retStatusObj.blockSeparation = "";
+
+ if (!EnigmailCore.getService().initialized) {
+ EnigmailLog.ERROR("encryption.jsm: encryptMessageEnd: not yet initialized\n");
+ retStatusObj.errorMsg = EnigmailLocale.getString("notInit");
+ return -1;
+ }
+
+ EnigmailErrorHandling.parseErrorOutput(stderrStr, retStatusObj);
+
+ exitCode = EnigmailExecution.fixExitCode(exitCode, retStatusObj);
+ if ((exitCode === 0) && !outputLen) {
+ exitCode = -1;
+ }
+
+ if (exitCode !== 0 && (signMsg || encryptMsg)) {
+ // GnuPG might return a non-zero exit code, even though the message was correctly
+ // signed or encryped -> try to fix the exit code
+
+ var correctedExitCode = 0;
+ if (signMsg) {
+ if (!(retStatusObj.statusFlags & EnigmailConstants.SIG_CREATED)) correctedExitCode = exitCode;
+ }
+ if (encryptMsg) {
+ if (!(retStatusObj.statusFlags & EnigmailConstants.END_ENCRYPTION)) correctedExitCode = exitCode;
+ }
+ exitCode = correctedExitCode;
+ }
+
+ EnigmailLog.DEBUG("encryption.jsm: encryptMessageEnd: command execution exit code: " + exitCode + "\n");
+
+ if (retStatusObj.statusFlags & EnigmailConstants.DISPLAY_MESSAGE) {
+ if (retStatusObj.extendedStatus.search(/\bdisp:/) >= 0) {
+ retStatusObj.errorMsg = retStatusObj.statusMsg;
+ }
+ else {
+ if (fromMailAddr.search(/^0x/) === 0) {
+ fromMailAddr = fromMailAddr.substr(2);
+ }
+ if (fromMailAddr.search(/^[A-F0-9]{8,40}$/i) === 0) {
+ fromMailAddr = "[A-F0-9]+" + fromMailAddr;
+ }
+
+ let s = new RegExp("^(\\[GNUPG:\\] )?INV_(RECP|SGNR) [0-9]+ (\\<|0x)?" + fromMailAddr + "\\>?", "m");
+ if (retStatusObj.statusMsg.search(s) >= 0) {
+ retStatusObj.errorMsg += "\n\n" + EnigmailLocale.getString("keyError.resolutionAction");
+ }
+ else if (retStatusObj.statusMsg.length > 0) {
+ retStatusObj.errorMsg = retStatusObj.statusMsg;
+ }
+ }
+ }
+ else if (retStatusObj.statusFlags & EnigmailConstants.INVALID_RECIPIENT) {
+ retStatusObj.errorMsg = retStatusObj.statusMsg;
+ }
+ else if (exitCode !== 0) {
+ retStatusObj.errorMsg = EnigmailLocale.getString("badCommand");
+ }
+
+ return exitCode;
+ },
+
+ encryptMessage: function(parent, uiFlags, plainText, fromMailAddr, toMailAddr, bccMailAddr, sendFlags,
+ exitCodeObj, statusFlagsObj, errorMsgObj) {
+ EnigmailLog.DEBUG("enigmail.js: Enigmail.encryptMessage: " + plainText.length + " bytes from " + fromMailAddr + " to " + toMailAddr + " (" + sendFlags + ")\n");
+
+ exitCodeObj.value = -1;
+ statusFlagsObj.value = 0;
+ errorMsgObj.value = "";
+
+ if (!plainText) {
+ EnigmailLog.DEBUG("enigmail.js: Enigmail.encryptMessage: NO ENCRYPTION!\n");
+ exitCodeObj.value = 0;
+ EnigmailLog.DEBUG(" <=== encryptMessage()\n");
+ return plainText;
+ }
+
+ var defaultSend = sendFlags & EnigmailConstants.SEND_DEFAULT;
+ var signMsg = sendFlags & EnigmailConstants.SEND_SIGNED;
+ var encryptMsg = sendFlags & EnigmailConstants.SEND_ENCRYPTED;
+
+ if (encryptMsg) {
+ // First convert all linebreaks to newlines
+ plainText = plainText.replace(/\r\n/g, "\n");
+ plainText = plainText.replace(/\r/g, "\n");
+
+ // we need all data in CRLF according to RFC 4880
+ plainText = plainText.replace(/\n/g, "\r\n");
+ }
+
+ var listener = EnigmailExecution.newSimpleListener(
+ function _stdin(pipe) {
+ pipe.write(plainText);
+ pipe.close();
+ },
+ function _done(exitCode) {});
+
+
+ var proc = EnigmailEncryption.encryptMessageStart(parent, uiFlags,
+ fromMailAddr, toMailAddr, bccMailAddr,
+ null, sendFlags,
+ listener, statusFlagsObj, errorMsgObj);
+ if (!proc) {
+ exitCodeObj.value = -1;
+ EnigmailLog.DEBUG(" <=== encryptMessage()\n");
+ return "";
+ }
+
+ // Wait for child pipes to close
+ proc.wait();
+
+ var retStatusObj = {};
+ exitCodeObj.value = EnigmailEncryption.encryptMessageEnd(fromMailAddr, EnigmailData.getUnicodeData(listener.stderrData), listener.exitCode,
+ uiFlags, sendFlags,
+ listener.stdoutData.length,
+ retStatusObj);
+
+ statusFlagsObj.value = retStatusObj.statusFlags;
+ statusFlagsObj.statusMsg = retStatusObj.statusMsg;
+ errorMsgObj.value = retStatusObj.errorMsg;
+
+
+ if ((exitCodeObj.value === 0) && listener.stdoutData.length === 0)
+ exitCodeObj.value = -1;
+
+ if (exitCodeObj.value === 0) {
+ // Normal return
+ EnigmailLog.DEBUG(" <=== encryptMessage()\n");
+ return EnigmailData.getUnicodeData(listener.stdoutData);
+ }
+
+ // Error processing
+ EnigmailLog.DEBUG("enigmail.js: Enigmail.encryptMessage: command execution exit code: " + exitCodeObj.value + "\n");
+ return "";
+ },
+
+ encryptAttachment: function(parent, fromMailAddr, toMailAddr, bccMailAddr, sendFlags, inFile, outFile,
+ exitCodeObj, statusFlagsObj, errorMsgObj) {
+ EnigmailLog.DEBUG("encryption.jsm: EnigmailEncryption.encryptAttachment infileName=" + inFile.path + "\n");
+
+ statusFlagsObj.value = 0;
+ sendFlags |= EnigmailConstants.SEND_ATTACHMENT;
+
+ let asciiArmor = false;
+ try {
+ asciiArmor = EnigmailPrefs.getPrefBranch().getBoolPref("inlineAttachAsciiArmor");
+ }
+ catch (ex) {}
+
+ const asciiFlags = (asciiArmor ? ENC_TYPE_ATTACH_ASCII : ENC_TYPE_ATTACH_BINARY);
+ let args = EnigmailEncryption.getEncryptCommand(fromMailAddr, toMailAddr, bccMailAddr, "", sendFlags, asciiFlags, errorMsgObj);
+
+ if (!args) {
+ return null;
+ }
+
+ const signMessage = (sendFlags & EnigmailConstants.SEND_SIGNED);
+
+ if (signMessage) {
+ args = args.concat(EnigmailPassword.command());
+ }
+
+ //const inFilePath = EnigmailFiles.getEscapedFilename(EnigmailFiles.getFilePathReadonly(inFile.QueryInterface(Ci.nsIFile)));
+ const fileContents = EnigmailFiles.readBinaryFile(inFile.QueryInterface(Ci.nsIFile));
+ const inFileName = inFile.QueryInterface(Ci.nsIFile).leafName;
+ const outFilePath = EnigmailFiles.getEscapedFilename(EnigmailFiles.getFilePathReadonly(outFile.QueryInterface(Ci.nsIFile)));
+
+ args = args.concat(["--yes", "-o", outFilePath, "--set-filename", inFileName]);
+
+ let cmdErrorMsgObj = {};
+
+ const msg = EnigmailExecution.execCmd(EnigmailGpgAgent.agentPath, args, fileContents, exitCodeObj, statusFlagsObj, {}, cmdErrorMsgObj);
+ if (exitCodeObj.value !== 0) {
+ if (cmdErrorMsgObj.value) {
+ errorMsgObj.value = EnigmailFiles.formatCmdLine(EnigmailGpgAgent.agentPath, args);
+ errorMsgObj.value += "\n" + cmdErrorMsgObj.value;
+ }
+ else {
+ errorMsgObj.value = "An unknown error has occurred";
+ }
+
+ return "";
+ }
+
+ return msg;
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/enigmailprocess_common.jsm
@@ -0,0 +1,752 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+"use strict";
+
+/* exported BaseProcess, PromiseWorker */
+/* global ChromeWorker: false, */
+
+var {
+ results: Cr
+} = Components;
+
+const XPCOMUtils = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm").XPCOMUtils;
+Components.utils.importGlobalProperties(["TextDecoder", "TextEncoder"]);
+
+var _AsyncShutdown, _setTimeout;
+
+var SubScriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader);
+SubScriptLoader.loadSubScript("chrome://openpgp/content/modules/enigmailprocess_shared.js", this);
+
+var EXPORTED_SYMBOLS = ["BaseProcess", "PromiseWorker", "SubprocessConstants"];
+
+const BUFFER_SIZE = 32768;
+
+let nextResponseId = 0;
+
+
+function getAsyncShutdown() {
+ if (!_AsyncShutdown) {
+ _AsyncShutdown = ChromeUtils.import("resource://gre/modules/AsyncShutdown.jsm").AsyncShutdown;
+ }
+ return _AsyncShutdown;
+}
+
+function getSetTimeout() {
+ if (!_setTimeout) {
+ _setTimeout = ChromeUtils.import("resource://gre/modules/Timer.jsm").setTimeout;
+ }
+ return _setTimeout;
+}
+
+/* global SubprocessConstants: true */
+
+/**
+ * Wraps a ChromeWorker so that messages sent to it return a promise which
+ * resolves when the message has been received and the operation it triggers is
+ * complete.
+ */
+class _PromiseWorker extends ChromeWorker {
+ constructor(url) {
+ super(url);
+
+ this.listeners = new Map();
+ this.pendingResponses = new Map();
+
+ this.addListener("close", this.onClose.bind(this));
+ this.addListener("failure", this.onFailure.bind(this));
+ this.addListener("success", this.onSuccess.bind(this));
+ this.addListener("debug", this.onDebug.bind(this));
+
+ this.addEventListener("message", this.onmessage);
+
+ this.shutdown = this.shutdown.bind(this);
+ getAsyncShutdown().webWorkersShutdown.addBlocker(
+ "Subprocess.jsm: Shut down IO worker",
+ this.shutdown);
+ }
+
+ onClose() {
+ getAsyncShutdown().webWorkersShutdown.removeBlocker(this.shutdown);
+ }
+
+ shutdown() {
+ return this.call("shutdown", []);
+ }
+
+ /**
+ * Adds a listener for the given message from the worker. Any message received
+ * from the worker with a `data.msg` property matching the given `msg`
+ * parameter are passed to the given listener.
+ *
+ * @param {string} msg
+ * The message to listen for.
+ * @param {function(Event)} listener
+ * The listener to call when matching messages are received.
+ */
+ addListener(msg, listener) {
+ if (!this.listeners.has(msg)) {
+ this.listeners.set(msg, new Set());
+ }
+ this.listeners.get(msg).add(listener);
+ }
+
+ /**
+ * Removes the given message listener.
+ *
+ * @param {string} msg
+ * The message to stop listening for.
+ * @param {function(Event)} listener
+ * The listener to remove.
+ */
+ removeListener(msg, listener) {
+ let listeners = this.listeners.get(msg);
+ if (listeners) {
+ listeners.delete(listener);
+
+ if (!listeners.size) {
+ this.listeners.delete(msg);
+ }
+ }
+ }
+
+ onmessage(event) {
+ let {
+ msg
+ } = event.data;
+ let listeners = this.listeners.get(msg) || new Set();
+
+ for (let listener of listeners) {
+ try {
+ listener(event.data);
+ }
+ catch (e) {
+ Cu.reportError(e);
+ }
+ }
+ }
+
+ /**
+ * Called when a message sent to the worker has failed, and rejects its
+ * corresponding promise.
+ *
+ * @private
+ */
+ onFailure({
+ msgId, error
+ }) {
+ this.pendingResponses.get(msgId).reject(error);
+ this.pendingResponses.delete(msgId);
+ }
+
+ /**
+ * Called when a message sent to the worker has succeeded, and resolves its
+ * corresponding promise.
+ *
+ * @private
+ */
+ onSuccess({
+ msgId, data
+ }) {
+ this.pendingResponses.get(msgId).resolve(data);
+ this.pendingResponses.delete(msgId);
+ }
+
+ onDebug({
+ message
+ }) {
+ //dump(`Worker debug: ${message}\n`);
+ }
+
+ /**
+ * Calls the given method in the worker, and returns a promise which resolves
+ * or rejects when the method has completed.
+ *
+ * @param {string} method
+ * The name of the method to call.
+ * @param {Array} args
+ * The arguments to pass to the method.
+ * @param {Array} [transferList]
+ * A list of objects to transfer to the worker, rather than cloning.
+ * @returns {Promise}
+ */
+ call(method, args, transferList = []) {
+ let msgId = nextResponseId++;
+
+ return new Promise((resolve, reject) => {
+ this.pendingResponses.set(msgId, {
+ resolve, reject
+ });
+
+ let message = {
+ msg: method,
+ msgId,
+ args
+ };
+
+ this.postMessage(message, transferList);
+ });
+ }
+}
+
+var PromiseWorker = _PromiseWorker;
+
+/**
+ * Represents an input or output pipe connected to a subprocess.
+ *
+ * @property {integer} fd
+ * The file descriptor number of the pipe on the child process's side.
+ * @readonly
+ */
+class Pipe {
+ /**
+ * @param {Process} process
+ * The child process that this pipe is connected to.
+ * @param {integer} fd
+ * The file descriptor number of the pipe on the child process's side.
+ * @param {integer} id
+ * The internal ID of the pipe, which ties it to the corresponding Pipe
+ * object on the Worker side.
+ */
+ constructor(process, fd, id) {
+ this.id = id;
+ this.fd = fd;
+ this.processId = process.id;
+ this.worker = process.worker;
+
+ /**
+ * @property {boolean} closed
+ * True if the file descriptor has been closed, and can no longer
+ * be read from or written to. Pending IO operations may still
+ * complete, but new operations may not be initiated.
+ * @readonly
+ */
+ this.closed = false;
+ }
+
+ /**
+ * Closes the end of the pipe which belongs to this process.
+ *
+ * @param {boolean} force
+ * If true, the pipe is closed immediately, regardless of any pending
+ * IO operations. If false, the pipe is closed after any existing
+ * pending IO operations have completed.
+ * @returns {Promise<object>}
+ * Resolves to an object with no properties once the pipe has been
+ * closed.
+ */
+ close(force = false) {
+ this.closed = true;
+ return this.worker.call("close", [this.id, force]);
+ }
+}
+
+/**
+ * Represents an output-only pipe, to which data may be written.
+ */
+class OutputPipe extends Pipe {
+ constructor(...args) {
+ super(...args);
+
+ this.encoder = new TextEncoder();
+ }
+
+ /**
+ * Writes the given data to the stream.
+ *
+ * When given an array buffer or typed array, ownership of the buffer is
+ * transferred to the IO worker, and it may no longer be used from this
+ * thread.
+ *
+ * @param {ArrayBuffer|TypedArray|string} buffer
+ * Data to write to the stream.
+ * @returns {Promise<object>}
+ * Resolves to an object with a `bytesWritten` property, containing
+ * the number of bytes successfully written, once the operation has
+ * completed.
+ *
+ * @rejects {object}
+ * May be rejected with an Error object, or an object with similar
+ * properties. The object will include an `errorCode` property with
+ * one of the following values if it was rejected for the
+ * corresponding reason:
+ *
+ * - Subprocess.ERROR_END_OF_FILE: The pipe was closed before
+ * all of the data in `buffer` could be written to it.
+ */
+ write(buffer) {
+ if (typeof buffer === "string") {
+ buffer = this.encoder.encode(buffer);
+ }
+
+ if (Cu.getClassName(buffer, true) !== "ArrayBuffer") {
+ if (buffer.byteLength === buffer.buffer.byteLength) {
+ buffer = buffer.buffer;
+ }
+ else {
+ buffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
+ }
+ }
+
+ let args = [this.id, buffer];
+
+ return this.worker.call("write", args, [buffer]);
+ }
+}
+
+/**
+ * Represents an input-only pipe, from which data may be read.
+ */
+class InputPipe extends Pipe {
+ constructor(...args) {
+ super(...args);
+
+ this.buffers = [];
+
+ /**
+ * @property {integer} dataAvailable
+ * The number of readable bytes currently stored in the input
+ * buffer.
+ * @readonly
+ */
+ this.dataAvailable = 0;
+
+ this.decoder = new TextDecoder();
+
+ this.pendingReads = [];
+
+ this._pendingBufferRead = null;
+
+ this.fillBuffer();
+ }
+
+ /**
+ * @property {integer} bufferSize
+ * The current size of the input buffer. This varies depending on
+ * the size of pending read operations.
+ * @readonly
+ */
+ get bufferSize() {
+ if (this.pendingReads.length) {
+ return Math.max(this.pendingReads[0].length, BUFFER_SIZE);
+ }
+ return BUFFER_SIZE;
+ }
+
+ /**
+ * Attempts to fill the input buffer.
+ *
+ * @private
+ */
+ fillBuffer() {
+ let dataWanted = this.bufferSize - this.dataAvailable;
+
+ if (!this._pendingBufferRead && dataWanted > 0) {
+ this._pendingBufferRead = this._read(dataWanted);
+
+ this._pendingBufferRead.then((result) => {
+ this._pendingBufferRead = null;
+
+ if (result) {
+ this.onInput(result.buffer);
+
+ this.fillBuffer();
+ }
+ });
+ }
+ }
+
+ _read(size) {
+ let args = [this.id, size];
+
+ return this.worker.call("read", args).catch(e => {
+ this.closed = true;
+
+ for (let {
+ length, resolve, reject
+ }
+ of this.pendingReads.splice(0)) {
+ if (length === null && e.errorCode === SubprocessConstants.ERROR_END_OF_FILE) {
+ resolve(new ArrayBuffer(0));
+ }
+ else {
+ reject(e);
+ }
+ }
+ });
+ }
+
+ /**
+ * Adds the given data to the end of the input buffer.
+ *
+ * @param {ArrayBuffer} buffer
+ * An input buffer to append to the current buffered input.
+ * @private
+ */
+ onInput(buffer) {
+ this.buffers.push(buffer);
+ this.dataAvailable += buffer.byteLength;
+ this.checkPendingReads();
+ }
+
+ /**
+ * Checks the topmost pending read operations and fulfills as many as can be
+ * filled from the current input buffer.
+ *
+ * @private
+ */
+ checkPendingReads() {
+ this.fillBuffer();
+
+ let reads = this.pendingReads;
+ while (reads.length && this.dataAvailable &&
+ reads[0].length <= this.dataAvailable) {
+ let pending = this.pendingReads.shift();
+
+ let length = pending.length || this.dataAvailable;
+
+ let result;
+ let byteLength = this.buffers[0].byteLength;
+ if (byteLength == length) {
+ result = this.buffers.shift();
+ }
+ else if (byteLength > length) {
+ let buffer = this.buffers[0];
+
+ this.buffers[0] = buffer.slice(length);
+ result = ArrayBuffer.transfer(buffer, length);
+ }
+ else {
+ result = ArrayBuffer.transfer(this.buffers.shift(), length);
+ let u8result = new Uint8Array(result);
+
+ while (byteLength < length) {
+ let buffer = this.buffers[0];
+ let u8buffer = new Uint8Array(buffer);
+
+ let remaining = length - byteLength;
+
+ if (buffer.byteLength <= remaining) {
+ this.buffers.shift();
+
+ u8result.set(u8buffer, byteLength);
+ }
+ else {
+ this.buffers[0] = buffer.slice(remaining);
+
+ u8result.set(u8buffer.subarray(0, remaining), byteLength);
+ }
+
+ byteLength += Math.min(buffer.byteLength, remaining);
+ }
+ }
+
+ this.dataAvailable -= result.byteLength;
+ pending.resolve(result);
+ }
+ }
+
+ /**
+ * Reads exactly `length` bytes of binary data from the input stream, or, if
+ * length is not provided, reads the first chunk of data to become available.
+ * In the latter case, returns an empty array buffer on end of file.
+ *
+ * The read operation will not complete until enough data is available to
+ * fulfill the request. If the pipe closes without enough available data to
+ * fulfill the read, the operation fails, and any remaining buffered data is
+ * lost.
+ *
+ * @param {integer} [length]
+ * The number of bytes to read.
+ * @returns {Promise<ArrayBuffer>}
+ *
+ * @rejects {object}
+ * May be rejected with an Error object, or an object with similar
+ * properties. The object will include an `errorCode` property with
+ * one of the following values if it was rejected for the
+ * corresponding reason:
+ *
+ * - Subprocess.ERROR_END_OF_FILE: The pipe was closed before
+ * enough input could be read to satisfy the request.
+ */
+ read(length = null) {
+ if (length !== null && !(Number.isInteger(length) && length >= 0)) {
+ throw new RangeError("Length must be a non-negative integer");
+ }
+
+ if (length == 0) {
+ return Promise.resolve(new ArrayBuffer(0));
+ }
+
+ return new Promise((resolve, reject) => {
+ this.pendingReads.push({
+ length, resolve, reject
+ });
+ this.checkPendingReads();
+ });
+ }
+
+ /**
+ * Reads exactly `length` bytes from the input stream, and parses them as
+ * UTF-8 JSON data.
+ *
+ * @param {integer} length
+ * The number of bytes to read.
+ * @returns {Promise<object>}
+ *
+ * @rejects {object}
+ * May be rejected with an Error object, or an object with similar
+ * properties. The object will include an `errorCode` property with
+ * one of the following values if it was rejected for the
+ * corresponding reason:
+ *
+ * - Subprocess.ERROR_END_OF_FILE: The pipe was closed before
+ * enough input could be read to satisfy the request.
+ * - Subprocess.ERROR_INVALID_JSON: The data read from the pipe
+ * could not be parsed as a valid JSON string.
+ */
+ readJSON(length) {
+ if (!Number.isInteger(length) || length <= 0) {
+ throw new RangeError("Length must be a positive integer");
+ }
+
+ return this.readString(length).then(string => {
+ try {
+ return JSON.parse(string);
+ }
+ catch (e) {
+ e.errorCode = SubprocessConstants.ERROR_INVALID_JSON;
+ throw e;
+ }
+ });
+ }
+
+ /**
+ * Reads a chunk of UTF-8 data from the input stream, and converts it to a
+ * JavaScript string.
+ *
+ * If `length` is provided, reads exactly `length` bytes. Otherwise, reads the
+ * first chunk of data to become available, and returns an empty string on end
+ * of file. In the latter case, the chunk is decoded in streaming mode, and
+ * any incomplete UTF-8 sequences at the end of a chunk are returned at the
+ * start of a subsequent read operation.
+ *
+ * @param {integer} [length]
+ * The number of bytes to read.
+ * @param {object} [options]
+ * An options object as expected by TextDecoder.decode.
+ * @returns {Promise<string>}
+ *
+ * @rejects {object}
+ * May be rejected with an Error object, or an object with similar
+ * properties. The object will include an `errorCode` property with
+ * one of the following values if it was rejected for the
+ * corresponding reason:
+ *
+ * - Subprocess.ERROR_END_OF_FILE: The pipe was closed before
+ * enough input could be read to satisfy the request.
+ */
+ readString(length = null, options = {
+ stream: length === null
+ }) {
+ if (length !== null && !(Number.isInteger(length) && length >= 0)) {
+ throw new RangeError("Length must be a non-negative integer");
+ }
+
+ return this.read(length).then(buffer => {
+ return this.decoder.decode(buffer, options);
+ });
+ }
+
+ /**
+ * Reads 4 bytes from the input stream, and parses them as an unsigned
+ * integer, in native byte order.
+ *
+ * @returns {Promise<integer>}
+ *
+ * @rejects {object}
+ * May be rejected with an Error object, or an object with similar
+ * properties. The object will include an `errorCode` property with
+ * one of the following values if it was rejected for the
+ * corresponding reason:
+ *
+ * - Subprocess.ERROR_END_OF_FILE: The pipe was closed before
+ * enough input could be read to satisfy the request.
+ */
+ readUint32() {
+ return this.read(4).then(buffer => {
+ return new Uint32Array(buffer)[0];
+ });
+ }
+}
+
+/**
+ * @class Process
+ * @extends BaseProcess
+ */
+
+/**
+ * Represents a currently-running process, and allows interaction with it.
+ */
+class _BaseProcess {
+ /**
+ * @param {PromiseWorker} worker
+ * The worker instance which owns the process.
+ * @param {integer} processId
+ * The internal ID of the Process object, which ties it to the
+ * corresponding process on the Worker side.
+ * @param {integer[]} fds
+ * An array of internal Pipe IDs, one for each standard file descriptor
+ * in the child process.
+ * @param {integer} pid
+ * The operating system process ID of the process.
+ */
+ constructor(worker, processId, fds, pid) {
+ this.id = processId;
+ this.worker = worker;
+
+ /**
+ * @property {integer} pid
+ * The process ID of the process, assigned by the operating system.
+ * @readonly
+ */
+ this.pid = pid;
+
+ this.exitCode = null;
+
+ this.exitPromise = new Promise(resolve => {
+ this.worker.call("wait", [this.id]).then(({
+ exitCode
+ }) => {
+ resolve(Object.freeze({
+ exitCode
+ }));
+ this.exitCode = exitCode;
+ });
+ });
+
+ if (fds[0] !== undefined) {
+ /**
+ * @property {OutputPipe} stdin
+ * A Pipe object which allows writing to the process's standard
+ * input.
+ * @readonly
+ */
+ this.stdin = new OutputPipe(this, 0, fds[0]);
+ }
+ if (fds[1] !== undefined) {
+ /**
+ * @property {InputPipe} stdout
+ * A Pipe object which allows reading from the process's standard
+ * output.
+ * @readonly
+ */
+ this.stdout = new InputPipe(this, 1, fds[1]);
+ }
+ if (fds[2] !== undefined) {
+ /**
+ * @property {InputPipe} [stderr]
+ * An optional Pipe object which allows reading from the
+ * process's standard error output.
+ * @readonly
+ */
+ this.stderr = new InputPipe(this, 2, fds[2]);
+ }
+ }
+
+ /**
+ * Spawns a process, and resolves to a BaseProcess instance on success.
+ *
+ * @param {object} options
+ * An options object as passed to `Subprocess.call`.
+ *
+ * @returns {Promise<BaseProcess>}
+ */
+ static create(options) {
+ let worker = this.getWorker();
+
+ return worker.call("spawn", [options]).then(({
+ processId, fds, pid
+ }) => {
+ return new this(worker, processId, fds, pid);
+ });
+ }
+
+ static get WORKER_URL() {
+ throw new Error("Not implemented");
+ }
+
+ static get WorkerClass() {
+ return PromiseWorker;
+ }
+
+ /**
+ * Gets the current subprocess worker, or spawns a new one if it does not
+ * currently exist.
+ *
+ * @returns {PromiseWorker}
+ */
+ static getWorker() {
+ if (!this._worker) {
+ this._worker = new this.WorkerClass(this.WORKER_URL);
+ }
+ return this._worker;
+ }
+
+ /**
+ * Kills the process.
+ *
+ * @param {integer} [timeout=300]
+ * A timeout, in milliseconds, after which the process will be forcibly
+ * killed. On platforms which support it, the process will be sent
+ * a `SIGTERM` signal immediately, so that it has a chance to terminate
+ * gracefully, and a `SIGKILL` signal if it hasn't exited within
+ * `timeout` milliseconds. On other platforms (namely Windows), the
+ * process will be forcibly terminated immediately.
+ *
+ * @returns {Promise<object>}
+ * Resolves to an object with an `exitCode` property when the process
+ * has exited.
+ */
+ kill(timeout = 300) {
+ // If the process has already exited, don't bother sending a signal.
+ if (this.exitCode !== null) {
+ return this.wait();
+ }
+
+ let force = timeout <= 0;
+ this.worker.call("kill", [this.id, force]);
+
+ if (!force) {
+ getSetTimeout()(() => {
+ if (this.exitCode === null) {
+ this.worker.call("kill", [this.id, true]);
+ }
+ }, timeout);
+ }
+
+ return this.wait();
+ }
+
+ /**
+ * Returns a promise which resolves to the process's exit code, once it has
+ * exited.
+ *
+ * @returns {Promise<object>}
+ * Resolves to an object with an `exitCode` property, containing the
+ * process's exit code, once the process has exited.
+ *
+ * On Unix-like systems, a negative exit code indicates that the
+ * process was killed by a signal whose signal number is the absolute
+ * value of the error code. On Windows, an exit code of -9 indicates
+ * that the process was killed via the {@linkcode BaseProcess#kill kill()}
+ * method.
+ */
+ wait() {
+ return this.exitPromise;
+ }
+}
+
+var BaseProcess = _BaseProcess;
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/enigmailprocess_main.jsm
@@ -0,0 +1,178 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+/*
+ * These modules are loosely based on the subprocess.jsm module created
+ * by Jan Gerber and Patrick Brunschwig, though the implementation
+ * differs drastically.
+ */
+
+"use strict";
+
+let EXPORTED_SYMBOLS = ["SubprocessMain"];
+
+/* exported SubprocessMain */
+
+var {
+ results: Cr
+} = Components;
+
+Components.utils.importGlobalProperties(["TextEncoder"]);
+const AppConstants = ChromeUtils.import("resource://gre/modules/AppConstants.jsm").AppConstants;
+const XPCOMUtils = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm").XPCOMUtils;
+var SubprocessConstants = ChromeUtils.import("chrome://openpgp/content/modules/enigmailprocess_common.jsm").SubprocessConstants;
+
+if (AppConstants.platform == "win") {
+ XPCOMUtils.defineLazyModuleGetter(this, "SubprocessImpl",
+ "chrome://openpgp/content/modules/enigmailprocess_win.jsm"); /* global SubprocessImpl: false */
+}
+else {
+ XPCOMUtils.defineLazyModuleGetter(this, "SubprocessImpl",
+ "chrome://openpgp/content/modules/enigmailprocess_unix.jsm");
+}
+
+function encodeEnvVar(name, value) {
+ if (typeof name === "string" && typeof value === "string") {
+ return `${name}=${value}`;
+ }
+
+ let encoder = new TextEncoder("utf-8");
+ function encode(val) {
+ return typeof val === "string" ? encoder.encode(val) : val;
+ }
+
+ return Uint8Array.of(...encode(name), ...encode("="), ...encode(value), 0);
+}
+
+/**
+ * Allows for creation of and communication with OS-level sub-processes.
+ * @namespace
+ */
+var SubprocessMain = {
+ /**
+ * Launches a process, and returns a handle to it.
+ *
+ * @param {object} options
+ * An object describing the process to launch.
+ *
+ * @param {string} options.command
+ * The full path of the execuable to launch. Relative paths are not
+ * accepted, and `$PATH` is not searched.
+ *
+ * If a path search is necessary, the {@link SubprocessMain.pathSearch} method may
+ * be used to map a bare executable name to a full path.
+ *
+ * @param {string[]} [options.arguments]
+ * A list of strings to pass as arguments to the process.
+ *
+ * @param {object} [options.environment]
+ * An object containing a key and value for each environment variable
+ * to pass to the process. Only the object's own, enumerable properties
+ * are added to the environment.
+ *
+ * @param {boolean} [options.environmentAppend]
+ * If true, append the environment variables passed in `environment` to
+ * the existing set of environment variables. Otherwise, the values in
+ * 'environment' constitute the entire set of environment variables
+ * passed to the new process.
+ *
+ * @param {string} [options.stderr]
+ * Defines how the process's stderr output is handled. One of:
+ *
+ * - `"ignore"`: (default) The process's standard error is not redirected.
+ * - `"stdout"`: The process's stderr is merged with its stdout.
+ * - `"pipe"`: The process's stderr is redirected to a pipe, which can be read
+ * from via its `stderr` property.
+ *
+ * @param {string} [options.workdir]
+ * The working directory in which to launch the new process.
+ *
+ * @returns {Promise<Process>}
+ *
+ * @rejects {Error}
+ * May be rejected with an Error object if the process can not be
+ * launched. The object will include an `errorCode` property with
+ * one of the following values if it was rejected for the
+ * corresponding reason:
+ *
+ * - SubprocessMain.ERROR_BAD_EXECUTABLE: The given command could not
+ * be found, or the file that it references is not executable.
+ *
+ * Note that if the process is successfully launched, but exits with
+ * a non-zero exit code, the promise will still resolve successfully.
+ */
+ call(options) {
+ options = Object.assign({}, options);
+
+ options.stderr = options.stderr || "ignore";
+ options.workdir = options.workdir || null;
+
+ let environment = {};
+ if (!options.environment || options.environmentAppend) {
+ environment = this.getEnvironment();
+ }
+
+ if (options.environment) {
+ Object.assign(environment, options.environment);
+ }
+
+ options.environment = Object.entries(environment)
+ .map(([key, val]) => encodeEnvVar(key, val));
+
+ options.arguments = Array.from(options.arguments || []);
+
+ return Promise.resolve(SubprocessImpl.isExecutableFile(options.command)).then(isExecutable => {
+ if (!isExecutable) {
+ let error = new Error(`File at path "${options.command}" does not exist, or is not executable`);
+ error.errorCode = SubprocessConstants.ERROR_BAD_EXECUTABLE;
+ throw error;
+ }
+
+ options.arguments.unshift(options.command);
+
+ return SubprocessImpl.call(options);
+ });
+ },
+
+ /**
+ * Returns an object with a key-value pair for every variable in the process's
+ * current environment.
+ *
+ * @returns {object}
+ */
+ getEnvironment() {
+ let environment = Object.create(null);
+ for (let [k, v] of SubprocessImpl.getEnvironment()) {
+ environment[k] = v;
+ }
+ return environment;
+ },
+
+ /**
+ * Searches for the given executable file in the system executable
+ * file paths as specified by the PATH environment variable.
+ *
+ * On Windows, if the unadorned filename cannot be found, the
+ * extensions in the semicolon-separated list in the PATHSEP
+ * environment variable are successively appended to the original
+ * name and searched for in turn.
+ *
+ * @param {string} command
+ * The name of the executable to find.
+ * @param {object} [environment]
+ * An object containing a key for each environment variable to be used
+ * in the search. If not provided, full the current process environment
+ * is used.
+ * @returns {Promise<string>}
+ */
+ pathSearch(command, environment = this.getEnvironment()) {
+ let path = SubprocessImpl.pathSearch(command, environment);
+ return Promise.resolve(path);
+ }
+};
+
+Object.assign(SubprocessMain, SubprocessConstants);
+Object.freeze(SubprocessMain);
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/enigmailprocess_shared.js
@@ -0,0 +1,102 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+"use strict";
+
+/* exported Library, SubprocessConstants */
+
+/* global ctypes: false */
+
+if (!ArrayBuffer.transfer) {
+ /**
+ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer/transfer
+ *
+ * @param {ArrayBuffer} buffer
+ * @param {integer} [size = buffer.byteLength]
+ * @returns {ArrayBuffer}
+ */
+ ArrayBuffer.transfer = function(buffer, size = buffer.byteLength) {
+ let u8out = new Uint8Array(size);
+ let u8buffer = new Uint8Array(buffer, 0, Math.min(size, buffer.byteLength));
+
+ u8out.set(u8buffer);
+
+ return u8out.buffer;
+ };
+}
+
+var libraries = {};
+
+class Library {
+ constructor(name, names, definitions) {
+ if (name in libraries) {
+ return libraries[name];
+ }
+
+ for (let name of names) {
+ try {
+ if (!this.library) {
+ this.library = ctypes.open(name);
+ }
+ } catch (e) {
+ // Ignore errors until we've tried all the options.
+ }
+ }
+ if (!this.library) {
+ throw new Error("Could not load libc");
+ }
+
+ libraries[name] = this;
+
+ for (let symbol of Object.keys(definitions)) {
+ this.declare(symbol, ...definitions[symbol]);
+ }
+
+ return this;
+ }
+
+ declare(name, ...args) {
+ Object.defineProperty(this, name, {
+ configurable: true,
+ get() {
+ Object.defineProperty(this, name, {
+ configurable: true,
+ value: this.library.declare(name, ...args)
+ });
+
+ return this[name];
+ }
+ });
+ }
+}
+
+/**
+ * Holds constants which apply to various Subprocess operations.
+ * @namespace
+ * @lends Subprocess
+ */
+var SubprocessConstants = {
+ /**
+ * @property {integer} ERROR_END_OF_FILE
+ * The operation failed because the end of the file was reached.
+ * @constant
+ */
+ ERROR_END_OF_FILE: 0xff7a0001,
+ /**
+ * @property {integer} ERROR_INVALID_JSON
+ * The operation failed because an invalid JSON was encountered.
+ * @constant
+ */
+ ERROR_INVALID_JSON: 0xff7a0002,
+ /**
+ * @property {integer} ERROR_BAD_EXECUTABLE
+ * The operation failed because the given file did not exist, or
+ * could not be executed.
+ * @constant
+ */
+ ERROR_BAD_EXECUTABLE: 0xff7a0003
+};
+
+Object.freeze(SubprocessConstants);
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/enigmailprocess_shared_unix.js
@@ -0,0 +1,169 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+"use strict";
+
+/* exported libc */
+
+/* global ctypes: false, OS: false, Library: false */
+
+const LIBC = OS.Constants.libc;
+
+const LIBC_CHOICES = ["libc.so", "libSystem.B.dylib", "a.out"];
+
+const unix = {
+ pid_t: ctypes.int32_t,
+
+ pollfd: new ctypes.StructType("pollfd", [{
+ "fd": ctypes.int
+ }, {
+ "events": ctypes.short
+ }, {
+ "revents": ctypes.short
+ }]),
+
+ posix_spawn_file_actions_t: ctypes.uint8_t.array(
+ LIBC.OSFILE_SIZEOF_POSIX_SPAWN_FILE_ACTIONS_T),
+
+ WEXITSTATUS(status) {
+ return (status >> 8) & 0xff;
+ },
+
+ WTERMSIG(status) {
+ return status & 0x7f;
+ }
+};
+
+var libc = new Library("libc", LIBC_CHOICES, {
+ environ: [ctypes.char.ptr.ptr],
+
+ // Darwin-only.
+ _NSGetEnviron: [
+ ctypes.default_abi,
+ ctypes.char.ptr.ptr.ptr
+ ],
+
+ setenv: [
+ ctypes.default_abi,
+ ctypes.int,
+ ctypes.char.ptr,
+ ctypes.char.ptr,
+ ctypes.int
+ ],
+
+ chdir: [
+ ctypes.default_abi,
+ ctypes.int,
+ ctypes.char.ptr /* path */
+ ],
+
+ close: [
+ ctypes.default_abi,
+ ctypes.int,
+ ctypes.int /* fildes */
+ ],
+
+ fcntl: [
+ ctypes.default_abi,
+ ctypes.int,
+ ctypes.int, /* fildes */
+ ctypes.int, /* cmd */
+ ctypes.int /* ... */
+ ],
+
+ getcwd: [
+ ctypes.default_abi,
+ ctypes.char.ptr,
+ ctypes.char.ptr, /* buf */
+ ctypes.size_t /* size */
+ ],
+
+ kill: [
+ ctypes.default_abi,
+ ctypes.int,
+ unix.pid_t, /* pid */
+ ctypes.int /* signal */
+ ],
+
+ pipe: [
+ ctypes.default_abi,
+ ctypes.int,
+ ctypes.int.array(2) /* pipefd */
+ ],
+
+ poll: [
+ ctypes.default_abi,
+ ctypes.int,
+ unix.pollfd.array(), /* fds */
+ ctypes.unsigned_int, /* nfds */
+ ctypes.int /* timeout */
+ ],
+
+ posix_spawn: [
+ ctypes.default_abi,
+ ctypes.int,
+ unix.pid_t.ptr, /* pid */
+ ctypes.char.ptr, /* path */
+ unix.posix_spawn_file_actions_t.ptr, /* file_actions */
+ ctypes.voidptr_t, /* attrp */
+ ctypes.char.ptr.ptr, /* argv */
+ ctypes.char.ptr.ptr /* envp */
+ ],
+
+ posix_spawn_file_actions_addclose: [
+ ctypes.default_abi,
+ ctypes.int,
+ unix.posix_spawn_file_actions_t.ptr, /* file_actions */
+ ctypes.int /* fildes */
+ ],
+
+ posix_spawn_file_actions_adddup2: [
+ ctypes.default_abi,
+ ctypes.int,
+ unix.posix_spawn_file_actions_t.ptr, /* file_actions */
+ ctypes.int, /* fildes */
+ ctypes.int /* newfildes */
+ ],
+
+ posix_spawn_file_actions_destroy: [
+ ctypes.default_abi,
+ ctypes.int,
+ unix.posix_spawn_file_actions_t.ptr /* file_actions */
+ ],
+
+ posix_spawn_file_actions_init: [
+ ctypes.default_abi,
+ ctypes.int,
+ unix.posix_spawn_file_actions_t.ptr /* file_actions */
+ ],
+
+ read: [
+ ctypes.default_abi,
+ ctypes.ssize_t,
+ ctypes.int, /* fildes */
+ ctypes.char.ptr, /* buf */
+ ctypes.size_t /* nbyte */
+ ],
+
+ waitpid: [
+ ctypes.default_abi,
+ unix.pid_t,
+ unix.pid_t, /* pid */
+ ctypes.int.ptr, /* status */
+ ctypes.int /* options */
+ ],
+
+ write: [
+ ctypes.default_abi,
+ ctypes.ssize_t,
+ ctypes.int, /* fildes */
+ ctypes.char.ptr, /* buf */
+ ctypes.size_t /* nbyte */
+ ]
+});
+
+unix.Fd = function(fd) {
+ return ctypes.CDataFinalizer(ctypes.int(fd), libc.close);
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/enigmailprocess_shared_win.js
@@ -0,0 +1,581 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+"use strict";
+
+/* exported LIBC, Win, createPipe, libc */
+
+/* global ctypes: false, OS: false, Library: false */
+/* eslint no-void: 0 */
+
+const LIBC = OS.Constants.libc;
+
+const Win = OS.Constants.Win;
+
+const LIBC_CHOICES = ["kernel32.dll"];
+
+var win32 = {
+ // On Windows 64, winapi_abi is an alias for default_abi.
+ WINAPI: ctypes.winapi_abi,
+
+ VOID: ctypes.void_t,
+
+ BYTE: ctypes.uint8_t,
+ WORD: ctypes.uint16_t,
+ DWORD: ctypes.uint32_t,
+ LONG: ctypes.long,
+ LARGE_INTEGER: ctypes.int64_t,
+ ULONGLONG: ctypes.uint64_t,
+
+ UINT: ctypes.unsigned_int,
+ UCHAR: ctypes.unsigned_char,
+
+ BOOL: ctypes.bool,
+
+ HANDLE: ctypes.voidptr_t,
+ PVOID: ctypes.voidptr_t,
+ LPVOID: ctypes.voidptr_t,
+
+ CHAR: ctypes.char,
+ WCHAR: ctypes.jschar,
+
+ ULONG_PTR: ctypes.uintptr_t,
+
+ SIZE_T: ctypes.size_t,
+ PSIZE_T: ctypes.size_t.ptr
+};
+
+Object.assign(win32, {
+ DWORD_PTR: win32.ULONG_PTR,
+
+ LPSTR: win32.CHAR.ptr,
+ LPWSTR: win32.WCHAR.ptr,
+
+ LPBYTE: win32.BYTE.ptr,
+ LPDWORD: win32.DWORD.ptr,
+ LPHANDLE: win32.HANDLE.ptr,
+
+ // This is an opaque type.
+ PROC_THREAD_ATTRIBUTE_LIST: ctypes.char.array(),
+ LPPROC_THREAD_ATTRIBUTE_LIST: ctypes.char.ptr
+});
+
+Object.assign(win32, {
+ LPCSTR: win32.LPSTR,
+ LPCWSTR: win32.LPWSTR,
+ LPCVOID: win32.LPVOID
+});
+
+Object.assign(win32, {
+ INVALID_HANDLE_VALUE: ctypes.cast(ctypes.int64_t(-1), win32.HANDLE),
+ NULL_HANDLE_VALUE: ctypes.cast(ctypes.uintptr_t(0), win32.HANDLE),
+ CREATE_SUSPENDED: 0x00000004,
+ CREATE_NEW_CONSOLE: 0x00000010,
+ CREATE_UNICODE_ENVIRONMENT: 0x00000400,
+ CREATE_NO_WINDOW: 0x08000000,
+ CREATE_BREAKAWAY_FROM_JOB: 0x01000000,
+ EXTENDED_STARTUPINFO_PRESENT: 0x00080000,
+
+ STARTF_USESTDHANDLES: 0x0100,
+
+ DUPLICATE_CLOSE_SOURCE: 0x01,
+ DUPLICATE_SAME_ACCESS: 0x02,
+
+ ERROR_HANDLE_EOF: 38,
+ ERROR_BROKEN_PIPE: 109,
+ ERROR_INSUFFICIENT_BUFFER: 122,
+
+ FILE_FLAG_OVERLAPPED: 0x40000000,
+
+ PIPE_TYPE_BYTE: 0x00,
+
+ PIPE_ACCESS_INBOUND: 0x01,
+ PIPE_ACCESS_OUTBOUND: 0x02,
+ PIPE_ACCESS_DUPLEX: 0x03,
+
+ PIPE_WAIT: 0x00,
+ PIPE_NOWAIT: 0x01,
+
+ STILL_ACTIVE: 259,
+
+ PROC_THREAD_ATTRIBUTE_HANDLE_LIST: 0x00020002,
+
+ JobObjectBasicLimitInformation: 2,
+ JobObjectExtendedLimitInformation: 9,
+
+ JOB_OBJECT_LIMIT_BREAKAWAY_OK: 0x00000800,
+
+ // These constants are 32-bit unsigned integers, but Windows defines
+ // them as negative integers cast to an unsigned type.
+ STD_INPUT_HANDLE: -10 + 0x100000000,
+ STD_OUTPUT_HANDLE: -11 + 0x100000000,
+ STD_ERROR_HANDLE: -12 + 0x100000000,
+
+ WAIT_TIMEOUT: 0x00000102,
+ WAIT_FAILED: 0xffffffff
+});
+
+Object.assign(win32, {
+ JOBOBJECT_BASIC_LIMIT_INFORMATION: new ctypes.StructType("JOBOBJECT_BASIC_LIMIT_INFORMATION", [{
+ "PerProcessUserTimeLimit": win32.LARGE_INTEGER
+ }, {
+ "PerJobUserTimeLimit": win32.LARGE_INTEGER
+ }, {
+ "LimitFlags": win32.DWORD
+ }, {
+ "MinimumWorkingSetSize": win32.SIZE_T
+ }, {
+ "MaximumWorkingSetSize": win32.SIZE_T
+ }, {
+ "ActiveProcessLimit": win32.DWORD
+ }, {
+ "Affinity": win32.ULONG_PTR
+ }, {
+ "PriorityClass": win32.DWORD
+ }, {
+ "SchedulingClass": win32.DWORD
+ }]),
+
+ IO_COUNTERS: new ctypes.StructType("IO_COUNTERS", [{
+ "ReadOperationCount": win32.ULONGLONG
+ }, {
+ "WriteOperationCount": win32.ULONGLONG
+ }, {
+ "OtherOperationCount": win32.ULONGLONG
+ }, {
+ "ReadTransferCount": win32.ULONGLONG
+ }, {
+ "WriteTransferCount": win32.ULONGLONG
+ }, {
+ "OtherTransferCount": win32.ULONGLONG
+ }])
+});
+
+Object.assign(win32, {
+ JOBOBJECT_EXTENDED_LIMIT_INFORMATION: new ctypes.StructType("JOBOBJECT_EXTENDED_LIMIT_INFORMATION", [{
+ "BasicLimitInformation": win32.JOBOBJECT_BASIC_LIMIT_INFORMATION
+ }, {
+ "IoInfo": win32.IO_COUNTERS
+ }, {
+ "ProcessMemoryLimit": win32.SIZE_T
+ }, {
+ "JobMemoryLimit": win32.SIZE_T
+ }, {
+ "PeakProcessMemoryUsed": win32.SIZE_T
+ }, {
+ "PeakJobMemoryUsed": win32.SIZE_T
+ }]),
+
+ OVERLAPPED: new ctypes.StructType("OVERLAPPED", [{
+ "Internal": win32.ULONG_PTR
+ }, {
+ "InternalHigh": win32.ULONG_PTR
+ }, {
+ "Offset": win32.DWORD
+ }, {
+ "OffsetHigh": win32.DWORD
+ }, {
+ "hEvent": win32.HANDLE
+ }]),
+
+ PROCESS_INFORMATION: new ctypes.StructType("PROCESS_INFORMATION", [{
+ "hProcess": win32.HANDLE
+ }, {
+ "hThread": win32.HANDLE
+ }, {
+ "dwProcessId": win32.DWORD
+ }, {
+ "dwThreadId": win32.DWORD
+ }]),
+
+ SECURITY_ATTRIBUTES: new ctypes.StructType("SECURITY_ATTRIBUTES", [{
+ "nLength": win32.DWORD
+ }, {
+ "lpSecurityDescriptor": win32.LPVOID
+ }, {
+ "bInheritHandle": win32.BOOL
+ }]),
+
+ STARTUPINFOW: new ctypes.StructType("STARTUPINFOW", [{
+ "cb": win32.DWORD
+ }, {
+ "lpReserved": win32.LPWSTR
+ }, {
+ "lpDesktop": win32.LPWSTR
+ }, {
+ "lpTitle": win32.LPWSTR
+ }, {
+ "dwX": win32.DWORD
+ }, {
+ "dwY": win32.DWORD
+ }, {
+ "dwXSize": win32.DWORD
+ }, {
+ "dwYSize": win32.DWORD
+ }, {
+ "dwXCountChars": win32.DWORD
+ }, {
+ "dwYCountChars": win32.DWORD
+ }, {
+ "dwFillAttribute": win32.DWORD
+ }, {
+ "dwFlags": win32.DWORD
+ }, {
+ "wShowWindow": win32.WORD
+ }, {
+ "cbReserved2": win32.WORD
+ }, {
+ "lpReserved2": win32.LPBYTE
+ }, {
+ "hStdInput": win32.HANDLE
+ }, {
+ "hStdOutput": win32.HANDLE
+ }, {
+ "hStdError": win32.HANDLE
+ }])
+});
+
+Object.assign(win32, {
+ STARTUPINFOEXW: new ctypes.StructType("STARTUPINFOEXW", [{
+ "StartupInfo": win32.STARTUPINFOW
+ }, {
+ "lpAttributeList": win32.LPPROC_THREAD_ATTRIBUTE_LIST
+ }])
+});
+
+
+var libc = new Library("libc", LIBC_CHOICES, {
+ AssignProcessToJobObject: [
+ win32.WINAPI,
+ win32.BOOL,
+ win32.HANDLE, /* hJob */
+ win32.HANDLE /* hProcess */
+ ],
+
+ CloseHandle: [
+ win32.WINAPI,
+ win32.BOOL,
+ win32.HANDLE /* hObject */
+ ],
+
+ CreateEventW: [
+ win32.WINAPI,
+ win32.HANDLE,
+ win32.SECURITY_ATTRIBUTES.ptr, /* opt lpEventAttributes */
+ win32.BOOL, /* bManualReset */
+ win32.BOOL, /* bInitialState */
+ win32.LPWSTR /* lpName */
+ ],
+
+ CreateFileW: [
+ win32.WINAPI,
+ win32.HANDLE,
+ win32.LPWSTR, /* lpFileName */
+ win32.DWORD, /* dwDesiredAccess */
+ win32.DWORD, /* dwShareMode */
+ win32.SECURITY_ATTRIBUTES.ptr, /* opt lpSecurityAttributes */
+ win32.DWORD, /* dwCreationDisposition */
+ win32.DWORD, /* dwFlagsAndAttributes */
+ win32.HANDLE /* opt hTemplateFile */
+ ],
+
+ CreateJobObjectW: [
+ win32.WINAPI,
+ win32.HANDLE,
+ win32.SECURITY_ATTRIBUTES.ptr, /* opt lpJobAttributes */
+ win32.LPWSTR /* lpName */
+ ],
+
+ CreateNamedPipeW: [
+ win32.WINAPI,
+ win32.HANDLE,
+ win32.LPWSTR, /* lpName */
+ win32.DWORD, /* dwOpenMode */
+ win32.DWORD, /* dwPipeMode */
+ win32.DWORD, /* nMaxInstances */
+ win32.DWORD, /* nOutBufferSize */
+ win32.DWORD, /* nInBufferSize */
+ win32.DWORD, /* nDefaultTimeOut */
+ win32.SECURITY_ATTRIBUTES.ptr /* opt lpSecurityAttributes */
+ ],
+
+ CreatePipe: [
+ win32.WINAPI,
+ win32.BOOL,
+ win32.LPHANDLE, /* out hReadPipe */
+ win32.LPHANDLE, /* out hWritePipe */
+ win32.SECURITY_ATTRIBUTES.ptr, /* opt lpPipeAttributes */
+ win32.DWORD /* nSize */
+ ],
+
+ CreateProcessW: [
+ win32.WINAPI,
+ win32.BOOL,
+ win32.LPCWSTR, /* lpApplicationName */
+ win32.LPWSTR, /* lpCommandLine */
+ win32.SECURITY_ATTRIBUTES.ptr, /* lpProcessAttributes */
+ win32.SECURITY_ATTRIBUTES.ptr, /* lpThreadAttributes */
+ win32.BOOL, /* bInheritHandle */
+ win32.DWORD, /* dwCreationFlags */
+ win32.LPVOID, /* opt lpEnvironment */
+ win32.LPCWSTR, /* opt lpCurrentDirectory */
+ win32.STARTUPINFOW.ptr, /* lpStartupInfo */
+ win32.PROCESS_INFORMATION.ptr /* out lpProcessInformation */
+ ],
+
+ CreateSemaphoreW: [
+ win32.WINAPI,
+ win32.HANDLE,
+ win32.SECURITY_ATTRIBUTES.ptr, /* opt lpSemaphoreAttributes */
+ win32.LONG, /* lInitialCount */
+ win32.LONG, /* lMaximumCount */
+ win32.LPCWSTR /* opt lpName */
+ ],
+
+ DeleteProcThreadAttributeList: [
+ win32.WINAPI,
+ win32.VOID,
+ win32.LPPROC_THREAD_ATTRIBUTE_LIST /* in/out lpAttributeList */
+ ],
+
+ DuplicateHandle: [
+ win32.WINAPI,
+ win32.BOOL,
+ win32.HANDLE, /* hSourceProcessHandle */
+ win32.HANDLE, /* hSourceHandle */
+ win32.HANDLE, /* hTargetProcessHandle */
+ win32.LPHANDLE, /* out lpTargetHandle */
+ win32.DWORD, /* dwDesiredAccess */
+ win32.BOOL, /* bInheritHandle */
+ win32.DWORD /* dwOptions */
+ ],
+
+ FreeEnvironmentStringsW: [
+ win32.WINAPI,
+ win32.BOOL,
+ win32.LPCWSTR /* lpszEnvironmentBlock */
+ ],
+
+ GetCurrentProcess: [
+ win32.WINAPI,
+ win32.HANDLE
+ ],
+
+ GetCurrentProcessId: [
+ win32.WINAPI,
+ win32.DWORD
+ ],
+
+ GetEnvironmentStringsW: [
+ win32.WINAPI,
+ win32.LPCWSTR
+ ],
+
+ GetExitCodeProcess: [
+ win32.WINAPI,
+ win32.BOOL,
+ win32.HANDLE, /* hProcess */
+ win32.LPDWORD /* lpExitCode */
+ ],
+
+ GetOverlappedResult: [
+ win32.WINAPI,
+ win32.BOOL,
+ win32.HANDLE, /* hFile */
+ win32.OVERLAPPED.ptr, /* lpOverlapped */
+ win32.LPDWORD, /* lpNumberOfBytesTransferred */
+ win32.BOOL /* bWait */
+ ],
+
+ GetStdHandle: [
+ win32.WINAPI,
+ win32.HANDLE,
+ win32.DWORD /* nStdHandle */
+ ],
+
+ InitializeProcThreadAttributeList: [
+ win32.WINAPI,
+ win32.BOOL,
+ win32.LPPROC_THREAD_ATTRIBUTE_LIST, /* out opt lpAttributeList */
+ win32.DWORD, /* dwAttributeCount */
+ win32.DWORD, /* dwFlags */
+ win32.PSIZE_T /* in/out lpSize */
+ ],
+
+ ReadFile: [
+ win32.WINAPI,
+ win32.BOOL,
+ win32.HANDLE, /* hFile */
+ win32.LPVOID, /* out lpBuffer */
+ win32.DWORD, /* nNumberOfBytesToRead */
+ win32.LPDWORD, /* opt out lpNumberOfBytesRead */
+ win32.OVERLAPPED.ptr /* opt in/out lpOverlapped */
+ ],
+
+ ReleaseSemaphore: [
+ win32.WINAPI,
+ win32.BOOL,
+ win32.HANDLE, /* hSemaphore */
+ win32.LONG, /* lReleaseCount */
+ win32.LONG.ptr /* opt out lpPreviousCount */
+ ],
+
+ ResumeThread: [
+ win32.WINAPI,
+ win32.DWORD,
+ win32.HANDLE /* hThread */
+ ],
+
+ SetInformationJobObject: [
+ win32.WINAPI,
+ win32.BOOL,
+ win32.HANDLE, /* hJob */
+ ctypes.int, /* JobObjectInfoClass */
+ win32.LPVOID, /* lpJobObjectInfo */
+ win32.DWORD /* cbJobObjectInfoLengt */
+ ],
+
+ TerminateJobObject: [
+ win32.WINAPI,
+ win32.BOOL,
+ win32.HANDLE, /* hJob */
+ win32.UINT /* uExitCode */
+ ],
+
+ TerminateProcess: [
+ win32.WINAPI,
+ win32.BOOL,
+ win32.HANDLE, /* hProcess */
+ win32.UINT /* uExitCode */
+ ],
+
+ UpdateProcThreadAttribute: [
+ win32.WINAPI,
+ win32.BOOL,
+ win32.LPPROC_THREAD_ATTRIBUTE_LIST, /* in/out lpAttributeList */
+ win32.DWORD, /* dwFlags */
+ win32.DWORD_PTR, /* Attribute */
+ win32.PVOID, /* lpValue */
+ win32.SIZE_T, /* cbSize */
+ win32.PVOID, /* out opt lpPreviousValue */
+ win32.PSIZE_T /* opt lpReturnSize */
+ ],
+
+ WaitForMultipleObjects: [
+ win32.WINAPI,
+ win32.DWORD,
+ win32.DWORD, /* nCount */
+ win32.HANDLE.ptr, /* hHandles */
+ win32.BOOL, /* bWaitAll */
+ win32.DWORD /* dwMilliseconds */
+ ],
+
+ WaitForSingleObject: [
+ win32.WINAPI,
+ win32.DWORD,
+ win32.HANDLE, /* hHandle */
+ win32.BOOL, /* bWaitAll */
+ win32.DWORD /* dwMilliseconds */
+ ],
+
+ WriteFile: [
+ win32.WINAPI,
+ win32.BOOL,
+ win32.HANDLE, /* hFile */
+ win32.LPCVOID, /* lpBuffer */
+ win32.DWORD, /* nNumberOfBytesToRead */
+ win32.LPDWORD, /* opt out lpNumberOfBytesWritten */
+ win32.OVERLAPPED.ptr /* opt in/out lpOverlapped */
+ ]
+});
+
+var user32 = new Library("user32", ["user32.dll"], {
+ AllowSetForegroundWindow: [
+ win32.WINAPI,
+ win32.BOOL,
+ win32.DWORD /* dwProcessId */
+ ]
+});
+
+let nextNamedPipeId = 0;
+
+win32.Handle = function(handle) {
+ return ctypes.CDataFinalizer(win32.HANDLE(handle), libc.CloseHandle);
+};
+
+win32.createPipe = function(secAttr, readFlags = 0, writeFlags = 0, size = 0) {
+ readFlags |= win32.PIPE_ACCESS_INBOUND;
+ writeFlags |= Win.FILE_ATTRIBUTE_NORMAL;
+
+ if (size == 0) {
+ size = 4096;
+ }
+
+ let pid = libc.GetCurrentProcessId();
+ const pipePrefix = "\\\\.\\Pipe\\SubProcessPipe";
+ let pipeName = String.raw `${pipePrefix}.${pid}.${nextNamedPipeId++}`;
+
+ let readHandle = libc.CreateNamedPipeW(
+ pipeName, readFlags,
+ win32.PIPE_TYPE_BYTE | win32.PIPE_WAIT,
+ 1, /* number of connections */
+ size, /* output buffer size */
+ size, /* input buffer size */
+ 0, /* timeout */
+ secAttr.address());
+
+ let isInvalid = handle => String(handle) == String(win32.HANDLE(Win.INVALID_HANDLE_VALUE));
+
+ if (isInvalid(readHandle)) {
+ return [];
+ }
+
+ let writeHandle = libc.CreateFileW(
+ pipeName, Win.GENERIC_WRITE, 0, secAttr.address(),
+ Win.OPEN_EXISTING, writeFlags, null);
+
+ if (isInvalid(writeHandle)) {
+ libc.CloseHandle(readHandle);
+ return [];
+ }
+
+ return [win32.Handle(readHandle),
+ win32.Handle(writeHandle)
+ ];
+};
+
+win32.createThreadAttributeList = function(handles) {
+ try {
+ void libc.InitializeProcThreadAttributeList;
+ void libc.DeleteProcThreadAttributeList;
+ void libc.UpdateProcThreadAttribute;
+ } catch (e) {
+ // This is only supported in Windows Vista and later.
+ return null;
+ }
+
+ let size = win32.SIZE_T();
+ if (!libc.InitializeProcThreadAttributeList(null, 1, 0, size.address()) &&
+ ctypes.winLastError != win32.ERROR_INSUFFICIENT_BUFFER) {
+ return null;
+ }
+
+ let attrList = win32.PROC_THREAD_ATTRIBUTE_LIST(size.value);
+
+ if (!libc.InitializeProcThreadAttributeList(attrList, 1, 0, size.address())) {
+ return null;
+ }
+
+ let ok = libc.UpdateProcThreadAttribute(
+ attrList, 0, win32.PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
+ handles, handles.constructor.size, null, null);
+
+ if (!ok) {
+ libc.DeleteProcThreadAttributeList(attrList);
+ return null;
+ }
+
+ return attrList;
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/enigmailprocess_unix.jsm
@@ -0,0 +1,209 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+"use strict";
+
+
+/* exported SubprocessImpl */
+
+/* globals BaseProcess, PromiseWorker */
+
+/* global libc: false, LIBC: false */
+
+var {
+ results: Cr
+} = Components;
+
+var EXPORTED_SYMBOLS = ["SubprocessImpl"];
+
+Components.utils.importGlobalProperties(["TextDecoder"]);
+const ctypes = ChromeUtils.import("resource://gre/modules/ctypes.jsm").ctypes;
+const OS = ChromeUtils.import("resource://gre/modules/osfile.jsm").OS;
+const Services = ChromeUtils.import("resource://gre/modules/Services.jsm").Services;
+var {
+ SubprocessConstants,
+ BaseProcess,
+ PromiseWorker
+} = ChromeUtils.import("chrome://openpgp/content/modules/enigmailprocess_common.jsm", this);
+
+Services.scriptloader.loadSubScript("chrome://openpgp/content/modules/enigmailprocess_shared.js", this);
+Services.scriptloader.loadSubScript("chrome://openpgp/content/modules/enigmailprocess_shared_unix.js", this);
+
+class UnixPromiseWorker extends PromiseWorker {
+ constructor(...args) {
+ super(...args);
+
+ let fds = ctypes.int.array(2)();
+ let res = libc.pipe(fds);
+ if (res == -1) {
+ throw new Error("Unable to create pipe");
+ }
+
+ this.signalFd = fds[1];
+
+ libc.fcntl(fds[0], LIBC.F_SETFL, LIBC.O_NONBLOCK);
+ libc.fcntl(fds[0], LIBC.F_SETFD, LIBC.FD_CLOEXEC);
+ libc.fcntl(fds[1], LIBC.F_SETFD, LIBC.FD_CLOEXEC);
+
+ this.call("init", [{
+ signalFd: fds[0]
+ }]);
+ }
+
+ closePipe() {
+ if (this.signalFd) {
+ libc.close(this.signalFd);
+ this.signalFd = null;
+ }
+ }
+
+ onClose() {
+ this.closePipe();
+ super.onClose();
+ }
+
+ signalWorker() {
+ libc.write(this.signalFd, new ArrayBuffer(1), 1);
+ }
+
+ postMessage(...args) {
+ this.signalWorker();
+ return super.postMessage(...args);
+ }
+}
+
+
+class Process extends BaseProcess {
+ static get WORKER_URL() {
+ return "chrome://openpgp/content/modules/enigmailprocess_worker_unix.js";
+ }
+
+ static get WorkerClass() {
+ return UnixPromiseWorker;
+ }
+}
+
+// Convert a null-terminated char pointer into a sized char array, and then
+// convert that into a JS typed array.
+// The resulting array will not be null-terminated.
+function ptrToUint8Array(input) {
+ let {
+ cast,
+ uint8_t
+ } = ctypes;
+
+ let len = 0;
+ for (let ptr = cast(input, uint8_t.ptr); ptr.contents; ptr = ptr.increment()) {
+ len++;
+ }
+
+ let aryPtr = cast(input, uint8_t.array(len).ptr);
+ return new Uint8Array(aryPtr.contents);
+}
+
+var SubprocessUnix = {
+ Process,
+
+ call(options) {
+ return Process.create(options);
+ },
+
+ * getEnvironment() {
+ let environ;
+ if (OS.Constants.Sys.Name == "Darwin") {
+ environ = libc._NSGetEnviron().contents;
+ } else {
+ environ = libc.environ;
+ }
+
+ const EQUAL = "=".charCodeAt(0);
+ let decoder = new TextDecoder("utf-8", {
+ fatal: true
+ });
+
+ function decode(array) {
+ try {
+ return decoder.decode(array);
+ } catch (e) {
+ return array;
+ }
+ }
+
+ for (let envp = environ; !envp.contents.isNull(); envp = envp.increment()) {
+ let buf = ptrToUint8Array(envp.contents);
+
+ for (let i = 0; i < buf.length; i++) {
+ if (buf[i] == EQUAL) {
+ yield [decode(buf.subarray(0, i)),
+ decode(buf.subarray(i + 1))
+ ];
+ break;
+ }
+ }
+ }
+ },
+
+ async isExecutableFile(path) {
+ if (!OS.Path.split(path).absolute) {
+ return false;
+ }
+
+ try {
+ let info = await OS.File.stat(path);
+
+ // FIXME: We really want access(path, X_OK) here, but OS.File does not
+ // support it.
+ return !info.isDir && (info.unixMode & 0x49);
+ } catch (e) {
+ return false;
+ }
+ },
+
+ /**
+ * Searches for the given executable file in the system executable
+ * file paths as specified by the PATH environment variable.
+ *
+ * On Windows, if the unadorned filename cannot be found, the
+ * extensions in the semicolon-separated list in the PATHEXT
+ * environment variable are successively appended to the original
+ * name and searched for in turn.
+ *
+ * @param {string} bin
+ * The name of the executable to find.
+ * @param {object} environment
+ * An object containing a key for each environment variable to be used
+ * in the search.
+ * @returns {Promise<string>}
+ */
+ async pathSearch(bin, environment) {
+ let split = OS.Path.split(bin);
+ if (split.absolute) {
+ if (await this.isExecutableFile(bin)) {
+ return bin;
+ }
+ let error = new Error(`File at path "${bin}" does not exist, or is not executable`);
+ error.errorCode = SubprocessConstants.ERROR_BAD_EXECUTABLE;
+ throw error;
+ }
+
+ let dirs = [];
+ if (typeof environment.PATH === "string") {
+ dirs = environment.PATH.split(":");
+ }
+
+ for (let dir of dirs) {
+ let path = OS.Path.join(dir, bin);
+
+ if (await this.isExecutableFile(path)) {
+ return path;
+ }
+ }
+ let error = new Error(`Executable not found: ${bin}`);
+ error.errorCode = SubprocessConstants.ERROR_BAD_EXECUTABLE;
+ throw error;
+ }
+};
+
+var SubprocessImpl = SubprocessUnix;
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/enigmailprocess_win.jsm
@@ -0,0 +1,176 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+"use strict";
+
+/* exported SubprocessImpl */
+
+/* global libc: false */
+
+/* globals BaseProcess, PromiseWorker */
+
+var {
+ results: Cr
+} = Components;
+
+var EXPORTED_SYMBOLS = ["SubprocessImpl"];
+
+const AppConstants = ChromeUtils.import("resource://gre/modules/AppConstants.jsm").AppConstants;
+const ctypes = ChromeUtils.import("resource://gre/modules/ctypes.jsm").ctypes;
+const OS = ChromeUtils.import("resource://gre/modules/osfile.jsm").OS;
+const Services = ChromeUtils.import("resource://gre/modules/Services.jsm").Services;
+const XPCOMUtils = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm").XPCOMUtils;
+var {
+ SubprocessConstants,
+ BaseProcess,
+ PromiseWorker
+} = ChromeUtils.import("chrome://openpgp/content/modules/enigmailprocess_common.jsm", this);
+
+XPCOMUtils.defineLazyServiceGetter(this, "env", "@mozilla.org/process/environment;1",
+ "nsIEnvironment"); /* global env: false */
+
+Services.scriptloader.loadSubScript("chrome://openpgp/content/modules/enigmailprocess_shared.js", this);
+Services.scriptloader.loadSubScript("chrome://openpgp/content/modules/enigmailprocess_shared_win.js", this);
+
+class WinPromiseWorker extends PromiseWorker {
+ constructor(...args) {
+ super(...args);
+
+ this.signalEvent = libc.CreateSemaphoreW(null, 0, 32, null);
+
+ this.call("init", [{
+ breakAwayFromJob: !AppConstants.isPlatformAndVersionAtLeast("win", "6.2"),
+ comspec: env.get("COMSPEC"),
+ signalEvent: String(ctypes.cast(this.signalEvent, ctypes.uintptr_t).value)
+ }]);
+ }
+
+ signalWorker() {
+ libc.ReleaseSemaphore(this.signalEvent, 1, null);
+ }
+
+ postMessage(...args) {
+ this.signalWorker();
+ return super.postMessage(...args);
+ }
+}
+
+class Process extends BaseProcess {
+ static get WORKER_URL() {
+ return "chrome://openpgp/content/modules/enigmailprocess_worker_win.js";
+ }
+
+ static get WorkerClass() {
+ return WinPromiseWorker;
+ }
+}
+
+var SubprocessWin = {
+ Process,
+
+ call(options) {
+ return Process.create(options);
+ },
+
+ * getEnvironment() {
+ let env = libc.GetEnvironmentStringsW();
+ try {
+ for (let p = env, q = env;; p = p.increment()) {
+ if (p.contents == "\0") {
+ if (String(p) == String(q)) {
+ break;
+ }
+
+ let str = q.readString();
+ q = p.increment();
+
+ let idx = str.indexOf("=");
+ if (idx == 0) {
+ idx = str.indexOf("=", 1);
+ }
+
+ if (idx >= 0) {
+ yield [str.slice(0, idx), str.slice(idx + 1)];
+ }
+ }
+ }
+ }
+ finally {
+ libc.FreeEnvironmentStringsW(env);
+ }
+ },
+
+ async isExecutableFile(path) {
+ if (!OS.Path.split(path).absolute) {
+ return false;
+ }
+
+ try {
+ let info = await OS.File.stat(path);
+ return !(info.isDir || info.isSymlink);
+ }
+ catch (e) {
+ return false;
+ }
+ },
+
+ /**
+ * Searches for the given executable file in the system executable
+ * file paths as specified by the PATH environment variable.
+ *
+ * On Windows, if the unadorned filename cannot be found, the
+ * extensions in the semicolon-separated list in the PATHEXT
+ * environment variable are successively appended to the original
+ * name and searched for in turn.
+ *
+ * @param {string} bin
+ * The name of the executable to find.
+ * @param {object} environment
+ * An object containing a key for each environment variable to be used
+ * in the search.
+ * @returns {Promise<string>}
+ */
+ async pathSearch(bin, environment) {
+ let split = OS.Path.split(bin);
+ if (split.absolute) {
+ if (await this.isExecutableFile(bin)) {
+ return bin;
+ }
+ let error = new Error(`File at path "${bin}" does not exist, or is not a normal file`);
+ error.errorCode = SubprocessConstants.ERROR_BAD_EXECUTABLE;
+ throw error;
+ }
+
+ let dirs = [];
+ let exts = [];
+ if (environment.PATH) {
+ dirs = environment.PATH.split(";");
+ }
+ if (environment.PATHEXT) {
+ exts = environment.PATHEXT.split(";");
+ }
+
+ for (let dir of dirs) {
+ let path = OS.Path.join(dir, bin);
+
+ if (await this.isExecutableFile(path)) {
+ return path;
+ }
+
+ for (let ext of exts) {
+ let file = path + ext;
+
+ if (await this.isExecutableFile(file)) {
+ return file;
+ }
+ }
+ }
+ let error = new Error(`Executable not found: ${bin}`);
+ error.errorCode = SubprocessConstants.ERROR_BAD_EXECUTABLE;
+ throw error;
+ }
+};
+
+var SubprocessImpl = SubprocessWin;
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/enigmailprocess_worker_common.js
@@ -0,0 +1,269 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+"use strict";
+
+/* exported BasePipe, BaseProcess, debug */
+/* globals Process, io */
+
+/* global ctypes: false */
+/* eslint no-console: 0 */
+
+function debug(message) {
+ self.postMessage({
+ msg: "debug",
+ message
+ });
+}
+
+class BasePipe {
+ constructor() {
+ this.closing = false;
+ this.closed = false;
+
+ this.closedPromise = new Promise(resolve => {
+ this.resolveClosed = resolve;
+ });
+
+ this.pending = [];
+ }
+
+ shiftPending() {
+ let result = this.pending.shift();
+
+ if (this.closing && this.pending.length == 0) {
+ this.close();
+ }
+
+ return result;
+ }
+}
+
+let nextProcessId = 0;
+
+class BaseProcess {
+ constructor(options) {
+ this.id = nextProcessId++;
+
+ this.exitCode = null;
+
+ this.exitPromise = new Promise(resolve => {
+ this.resolveExit = resolve;
+ });
+ this.exitPromise.then(() => {
+ // The input file descriptors will be closed after poll
+ // reports that their input buffers are empty. If we close
+ // them now, we may lose output.
+ this.pipes[0].close(true);
+ });
+
+ this.pid = null;
+ this.pipes = [];
+
+ this.stringArrays = [];
+
+ this.spawn(options);
+ }
+
+ /**
+ * Waits for the process to exit and all of its pending IO operations to
+ * complete.
+ *
+ * @returns {Promise<void>}
+ */
+ awaitFinished() {
+ return Promise.all([
+ this.exitPromise,
+ ...this.pipes.map(pipe => pipe.closedPromise)
+ ]);
+ }
+
+ /**
+ * Creates a null-terminated array of pointers to null-terminated C-strings,
+ * and returns it.
+ *
+ * @param {string[]} strings
+ * The strings to convert into a C string array.
+ *
+ * @returns {ctypes.char.ptr.array}
+ */
+ stringArray(strings) {
+ let result = ctypes.char.ptr.array(strings.length + 1)();
+
+ let cstrings = strings.map(str => ctypes.char.array()(str));
+ for (let [i, cstring] of cstrings.entries()) {
+ result[i] = cstring;
+ }
+
+ // Char arrays used in char arg and environment vectors must be
+ // explicitly kept alive in a JS object, or they will be reaped
+ // by the GC if it runs before our process is started.
+ this.stringArrays.push(cstrings);
+
+ return result;
+ }
+}
+
+let requests = {
+ init(details) {
+ io.init(details);
+
+ return {
+ data: {}
+ };
+ },
+
+ shutdown() {
+ io.shutdown();
+
+ return {
+ data: {}
+ };
+ },
+
+ close(pipeId, force = false) {
+ let pipe = io.getPipe(pipeId);
+
+ return pipe.close(force).then(() => ({
+ data: {}
+ }));
+ },
+
+ spawn(options) {
+ let process = new Process(options);
+ let processId = process.id;
+
+ io.addProcess(process);
+
+ let fds = process.pipes.map(pipe => pipe.id);
+
+ return {
+ data: {
+ processId,
+ fds,
+ pid: process.pid
+ }
+ };
+ },
+
+ kill(processId, force = false) {
+ let process = io.getProcess(processId);
+
+ process.kill(force ? 9 : 15);
+
+ return {
+ data: {}
+ };
+ },
+
+ wait(processId) {
+ let process = io.getProcess(processId);
+
+ process.wait();
+
+ process.awaitFinished().then(() => {
+ io.cleanupProcess(process);
+ });
+
+ return process.exitPromise.then(exitCode => {
+ return {
+ data: {
+ exitCode
+ }
+ };
+ });
+ },
+
+ read(pipeId, count) {
+ let pipe = io.getPipe(pipeId);
+
+ return pipe.read(count).then(buffer => {
+ return {
+ data: {
+ buffer
+ }
+ };
+ });
+ },
+
+ write(pipeId, buffer) {
+ let pipe = io.getPipe(pipeId);
+
+ return pipe.write(buffer).then(bytesWritten => {
+ return {
+ data: {
+ bytesWritten
+ }
+ };
+ });
+ },
+
+ getOpenFiles() {
+ return {
+ data: new Set(io.pipes.keys())
+ };
+ },
+
+ getProcesses() {
+ let data = new Map(Array.from(io.processes.values())
+ .filter(proc => proc.exitCode === null)
+ .map(proc => [proc.id, proc.pid]));
+ return {
+ data
+ };
+ },
+
+ waitForNoProcesses() {
+ return Promise.all(Array.from(io.processes.values(),
+ proc => proc.awaitFinished()));
+ }
+};
+
+onmessage = event => {
+ io.messageCount--;
+
+ let {
+ msg,
+ msgId,
+ args
+ } = event.data;
+
+ new Promise(resolve => {
+ resolve(requests[msg](...args));
+ }).then(result => {
+ let response = {
+ msg: "success",
+ msgId,
+ data: result.data
+ };
+
+ self.postMessage(response, result.transfer || []);
+ }).catch(error => {
+ if (error instanceof Error) {
+ error = {
+ message: error.message,
+ fileName: error.fileName,
+ lineNumber: error.lineNumber,
+ column: error.column,
+ stack: error.stack,
+ errorCode: error.errorCode
+ };
+ }
+
+ self.postMessage({
+ msg: "failure",
+ msgId,
+ error
+ });
+ }).catch(error => {
+ console.error(error);
+
+ self.postMessage({
+ msg: "failure",
+ msgId,
+ error: {}
+ });
+ });
+};
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/enigmailprocess_worker_unix.js
@@ -0,0 +1,650 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+"use strict";
+
+/* exported Process */
+/* globals BaseProcess, BasePipe */
+
+/* global importScripts: false */
+
+importScripts("chrome://openpgp/content/modules/enigmailprocess_shared.js",
+ "chrome://openpgp/content/modules/enigmailprocess_shared_unix.js",
+ "chrome://openpgp/content/modules/enigmailprocess_worker_common.js");
+
+/* global ctypes: false, LIBC: false, libc: false, unix: false, SubprocessConstants: false */
+/* global debug: false */
+/* eslint no-console: 0 */
+
+const POLL_TIMEOUT = 5000;
+
+let io;
+
+let nextPipeId = 0;
+
+class Pipe extends BasePipe {
+ constructor(process, fd) {
+ super();
+
+ this.process = process;
+ this.fd = fd;
+ this.id = nextPipeId++;
+ }
+
+ get pollEvents() {
+ throw new Error("Not implemented");
+ }
+
+ /**
+ * Closes the file descriptor.
+ *
+ * @param {boolean} [force=false]
+ * If true, the file descriptor is closed immediately. If false, the
+ * file descriptor is closed after all current pending IO operations
+ * have completed.
+ *
+ * @returns {Promise<void>}
+ * Resolves when the file descriptor has been closed.
+ */
+ close(force = false) {
+ if (!force && this.pending.length) {
+ this.closing = true;
+ return this.closedPromise;
+ }
+
+ for (let {
+ reject
+ }
+ of this.pending) {
+ let error = new Error("File closed");
+ error.errorCode = SubprocessConstants.ERROR_END_OF_FILE;
+ reject(error);
+ }
+ this.pending.length = 0;
+
+ if (!this.closed) {
+ this.fd.dispose();
+
+ this.closed = true;
+ this.resolveClosed();
+
+ io.pipes.delete(this.id);
+ io.updatePollFds();
+ }
+ return this.closedPromise;
+ }
+
+ /**
+ * Called when an error occurred while polling our file descriptor.
+ */
+ onError() {
+ this.close(true);
+ this.process.wait();
+ }
+}
+
+class InputPipe extends Pipe {
+ /**
+ * A bit mask of poll() events which we currently wish to be notified of on
+ * this file descriptor.
+ */
+ get pollEvents() {
+ if (this.pending.length) {
+ return LIBC.POLLIN;
+ }
+ return 0;
+ }
+
+ /**
+ * Asynchronously reads at most `length` bytes of binary data from the file
+ * descriptor into an ArrayBuffer of the same size. Returns a promise which
+ * resolves when the operation is complete.
+ *
+ * @param {integer} length
+ * The number of bytes to read.
+ *
+ * @returns {Promise<ArrayBuffer>}
+ */
+ read(length) {
+ if (this.closing || this.closed) {
+ throw new Error("Attempt to read from closed pipe");
+ }
+
+ return new Promise((resolve, reject) => {
+ this.pending.push({
+ resolve, reject, length
+ });
+ io.updatePollFds();
+ });
+ }
+
+ /**
+ * Synchronously reads at most `count` bytes of binary data into an
+ * ArrayBuffer, and returns that buffer. If no data can be read without
+ * blocking, returns null instead.
+ *
+ * @param {integer} count
+ * The number of bytes to read.
+ *
+ * @returns {ArrayBuffer|null}
+ */
+ readBuffer(count) {
+ let buffer = new ArrayBuffer(count);
+
+ let read = Number(libc.read(this.fd, buffer, buffer.byteLength));
+ if (read < 0 && ctypes.errno != LIBC.EAGAIN) {
+ this.onError();
+ }
+
+ if (read <= 0) {
+ return null;
+ }
+
+ if (read < buffer.byteLength) {
+ return ArrayBuffer.transfer(buffer, read);
+ }
+
+ return buffer;
+ }
+
+ /**
+ * Called when one of the IO operations matching the `pollEvents` mask may be
+ * performed without blocking.
+ *
+ * @returns {boolean}
+ * True if any data was successfully read.
+ */
+ onReady() {
+ let result = false;
+ let reads = this.pending;
+ while (reads.length) {
+ let {
+ resolve, length
+ } = reads[0];
+
+ let buffer = this.readBuffer(length);
+ if (buffer) {
+ result = true;
+ this.shiftPending();
+ resolve(buffer);
+ }
+ else {
+ break;
+ }
+ }
+
+ if (reads.length == 0) {
+ io.updatePollFds();
+ }
+ return result;
+ }
+}
+
+class OutputPipe extends Pipe {
+ /**
+ * A bit mask of poll() events which we currently wish to be notified of on
+ * this file discriptor.
+ */
+ get pollEvents() {
+ if (this.pending.length) {
+ return LIBC.POLLOUT;
+ }
+ return 0;
+ }
+
+ /**
+ * Asynchronously writes the given buffer to our file descriptor, and returns
+ * a promise which resolves when the operation is complete.
+ *
+ * @param {ArrayBuffer} buffer
+ * The buffer to write.
+ *
+ * @returns {Promise<integer>}
+ * Resolves to the number of bytes written when the operation is
+ * complete.
+ */
+ write(buffer) {
+ if (this.closing || this.closed) {
+ throw new Error("Attempt to write to closed pipe");
+ }
+
+ return new Promise((resolve, reject) => {
+ this.pending.push({
+ resolve, reject, buffer, length: buffer.byteLength
+ });
+ io.updatePollFds();
+ });
+ }
+
+ /**
+ * Attempts to synchronously write the given buffer to our file descriptor.
+ * Writes only as many bytes as can be written without blocking, and returns
+ * the number of byes successfully written.
+ *
+ * Closes the file descriptor if an IO error occurs.
+ *
+ * @param {ArrayBuffer} buffer
+ * The buffer to write.
+ *
+ * @returns {integer}
+ * The number of bytes successfully written.
+ */
+ writeBuffer(buffer) {
+ let bytesWritten = libc.write(this.fd, buffer, buffer.byteLength);
+
+ if (bytesWritten < 0 && ctypes.errno != LIBC.EAGAIN) {
+ this.onError();
+ }
+
+ return bytesWritten;
+ }
+
+ /**
+ * Called when one of the IO operations matching the `pollEvents` mask may be
+ * performed without blocking.
+ */
+ onReady() {
+ let writes = this.pending;
+ while (writes.length) {
+ let {
+ buffer, resolve, length
+ } = writes[0];
+
+ let written = this.writeBuffer(buffer);
+
+ if (written == buffer.byteLength) {
+ resolve(length);
+ this.shiftPending();
+ }
+ else if (written > 0) {
+ writes[0].buffer = buffer.slice(written);
+ }
+ else {
+ break;
+ }
+ }
+
+ if (writes.length == 0) {
+ io.updatePollFds();
+ }
+ }
+}
+
+class Signal {
+ constructor(fd) {
+ this.fd = fd;
+ }
+
+ cleanup() {
+ libc.close(this.fd);
+ this.fd = null;
+ }
+
+ get pollEvents() {
+ return LIBC.POLLIN;
+ }
+
+ /**
+ * Called when an error occurred while polling our file descriptor.
+ */
+ onError() {
+ io.shutdown();
+ }
+
+ /**
+ * Called when one of the IO operations matching the `pollEvents` mask may be
+ * performed without blocking.
+ */
+ onReady() {
+ let buffer = new ArrayBuffer(16);
+ let count = Number(libc.read(this.fd, buffer, buffer.byteLength));
+ if (count > 0) {
+ io.messageCount += count;
+ }
+ }
+}
+
+class Process extends BaseProcess {
+ /**
+ * Each Process object opens an additional pipe from the target object, which
+ * will be automatically closed when the process exits, but otherwise
+ * carries no data.
+ *
+ * This property contains a bit mask of poll() events which we wish to be
+ * notified of on this descriptor. We're not expecting any input from this
+ * pipe, but we need to poll for input until the process exits in order to be
+ * notified when the pipe closes.
+ */
+ get pollEvents() {
+ if (this.exitCode === null) {
+ return LIBC.POLLIN;
+ }
+ return 0;
+ }
+
+ /**
+ * Kills the process with the given signal.
+ *
+ * @param {integer} signal
+ */
+ kill(signal) {
+ libc.kill(this.pid, signal);
+ this.wait();
+ }
+
+ /**
+ * Initializes the IO pipes for use as standard input, output, and error
+ * descriptors in the spawned process.
+ *
+ * @param {object} options
+ * The Subprocess options object for this process.
+ * @returns {unix.Fd[]}
+ * The array of file descriptors belonging to the spawned process.
+ */
+ initPipes(options) {
+ let stderr = options.stderr;
+
+ let our_pipes = [];
+ let their_pipes = new Map();
+
+ let pipe = (input, id) => {
+ let fds = ctypes.int.array(2)();
+
+ let res = libc.pipe(fds);
+ if (res == -1) {
+ throw new Error("Unable to create pipe");
+ }
+
+ fds = Array.from(fds, unix.Fd);
+
+ if (input) {
+ fds.reverse();
+ }
+
+ if (input) {
+ our_pipes[id] = new InputPipe(this, fds[1]);
+ }
+ else {
+ our_pipes[id] = new OutputPipe(this, fds[1]);
+ }
+
+ libc.fcntl(fds[0], LIBC.F_SETFD, LIBC.FD_CLOEXEC);
+ libc.fcntl(fds[1], LIBC.F_SETFD, LIBC.FD_CLOEXEC);
+ libc.fcntl(fds[1], LIBC.F_SETFL, LIBC.O_NONBLOCK);
+
+ return fds[0];
+ };
+
+ their_pipes.set(0, pipe(false, 0));
+ their_pipes.set(1, pipe(true, 1));
+
+ if (stderr == "pipe") {
+ their_pipes.set(2, pipe(true, 2));
+ }
+ else if (stderr == "stdout") {
+ their_pipes.set(2, their_pipes.get(1));
+ }
+
+ // Create an additional pipe that we can use to monitor for process exit.
+ their_pipes.set(3, pipe(true, 3));
+ this.fd = our_pipes[3].fd;
+ delete our_pipes[3];
+
+ this.pipes = our_pipes;
+
+ return their_pipes;
+ }
+
+ spawn(options) {
+ let {
+ command, arguments: args
+ } = options;
+
+ let argv = this.stringArray(args);
+ let envp = this.stringArray(options.environment);
+
+ let actions = unix.posix_spawn_file_actions_t();
+ let actionsp = actions.address();
+
+ let fds = this.initPipes(options);
+
+ let cwd;
+ try {
+ if (options.workdir) {
+ cwd = ctypes.char.array(LIBC.PATH_MAX)();
+ libc.getcwd(cwd, cwd.length);
+
+ if (libc.chdir(options.workdir) < 0) {
+ throw new Error(`Unable to change working directory to ${options.workdir}`);
+ }
+ }
+
+ libc.posix_spawn_file_actions_init(actionsp);
+ for (let [i, fd] of fds.entries()) {
+ libc.posix_spawn_file_actions_adddup2(actionsp, fd, i);
+ }
+
+ let pid = unix.pid_t();
+ let rv = libc.posix_spawn(pid.address(), command, actionsp, null, argv, envp);
+
+ if (rv != 0) {
+ for (let pipe of this.pipes) {
+ pipe.close();
+ }
+ throw new Error(`Failed to execute command "${command}"`);
+ }
+
+ this.pid = pid.value;
+ }
+ finally {
+ libc.posix_spawn_file_actions_destroy(actionsp);
+
+ this.stringArrays.length = 0;
+
+ if (cwd) {
+ libc.chdir(cwd);
+ }
+ for (let fd of new Set(fds.values())) {
+ fd.dispose();
+ }
+ }
+ }
+
+ /**
+ * Called when input is available on our sentinel file descriptor.
+ *
+ * @see pollEvents
+ */
+ onReady() {
+ // We're not actually expecting any input on this pipe. If we get any, we
+ // can't poll the pipe any further without reading it.
+ if (this.wait() == undefined) {
+ this.kill(9);
+ }
+ }
+
+ /**
+ * Called when an error occurred while polling our sentinel file descriptor.
+ *
+ * @see pollEvents
+ */
+ onError() {
+ this.wait();
+ }
+
+ /**
+ * Attempts to wait for the process's exit status, without blocking. If
+ * successful, resolves the `exitPromise` to the process's exit value.
+ *
+ * @returns {integer|null}
+ * The process's exit status, if it has already exited.
+ */
+ wait() {
+ if (this.exitCode !== null) {
+ return this.exitCode;
+ }
+
+ let status = ctypes.int();
+
+ let res = libc.waitpid(this.pid, status.address(), LIBC.WNOHANG);
+
+ const EINTR = 4; // TODO: change to LIBC.EINTR with TB 59
+
+ // If there's a failure here and we get any errno other than EINTR, it
+ // means that the process has been reaped by another thread (most likely
+ // the nspr process wait thread), and its actual exit status is not
+ // available to us. In that case, we have to assume success.
+ if (res == 0 || (res == -1 && ctypes.errno == EINTR)) {
+ return null;
+ }
+
+ let sig = unix.WTERMSIG(status.value);
+ if (sig) {
+ this.exitCode = -sig;
+ }
+ else {
+ this.exitCode = unix.WEXITSTATUS(status.value);
+ }
+
+ this.fd.dispose();
+ io.updatePollFds();
+ this.resolveExit(this.exitCode);
+ return this.exitCode;
+ }
+}
+
+io = {
+ pollFds: null,
+ pollHandlers: null,
+
+ pipes: new Map(),
+
+ processes: new Map(),
+
+ messageCount: 0,
+
+ running: true,
+
+ init(details) {
+ this.signal = new Signal(details.signalFd);
+ this.updatePollFds();
+
+ setTimeout(this.loop.bind(this), 0);
+ },
+
+ shutdown() {
+ if (this.running) {
+ this.running = false;
+
+ this.signal.cleanup();
+ this.signal = null;
+
+ self.postMessage({
+ msg: "close"
+ });
+ self.close();
+ }
+ },
+
+ getPipe(pipeId) {
+ let pipe = this.pipes.get(pipeId);
+
+ if (!pipe) {
+ let error = new Error("File closed");
+ error.errorCode = SubprocessConstants.ERROR_END_OF_FILE;
+ throw error;
+ }
+ return pipe;
+ },
+
+ getProcess(processId) {
+ let process = this.processes.get(processId);
+
+ if (!process) {
+ throw new Error(`Invalid process ID: ${processId}`);
+ }
+ return process;
+ },
+
+ updatePollFds() {
+ let handlers = [this.signal,
+ ...this.pipes.values(),
+ ...this.processes.values()
+ ];
+
+ handlers = handlers.filter(handler => handler.pollEvents);
+
+ let pollfds = unix.pollfd.array(handlers.length)();
+
+ for (let [i, handler] of handlers.entries()) {
+ let pollfd = pollfds[i];
+
+ pollfd.fd = handler.fd;
+ pollfd.events = handler.pollEvents;
+ pollfd.revents = 0;
+ }
+
+ this.pollFds = pollfds;
+ this.pollHandlers = handlers;
+ },
+
+ loop() {
+ this.poll();
+ if (this.running) {
+ setTimeout(this.loop.bind(this), 0);
+ }
+ },
+
+ poll() {
+ let handlers = this.pollHandlers;
+ let pollfds = this.pollFds;
+
+ let timeout = this.messageCount > 0 ? 0 : POLL_TIMEOUT;
+ let count = libc.poll(pollfds, pollfds.length, timeout);
+
+ for (let i = 0; count && i < pollfds.length; i++) {
+ let pollfd = pollfds[i];
+ if (pollfd.revents) {
+ count--;
+
+ let handler = handlers[i];
+ try {
+ let success = false;
+ if (pollfd.revents & handler.pollEvents) {
+ success = handler.onReady();
+ }
+ // Only call the error handler in this iteration if we didn't also
+ // have a success. This is necessary because Linux systems set POLLHUP
+ // on a pipe when it's closed but there's still buffered data to be
+ // read, and Darwin sets POLLIN and POLLHUP on a closed pipe, even
+ // when there's no data to be read.
+ if (!success && (pollfd.revents & (LIBC.POLLERR | LIBC.POLLHUP | LIBC.POLLNVAL))) {
+ handler.onError();
+ }
+ }
+ catch (e) {
+ console.error(e);
+ debug(`Worker error: ${e} :: ${e.stack}`);
+ handler.onError();
+ }
+
+ pollfd.revents = 0;
+ }
+ }
+ },
+
+ addProcess(process) {
+ this.processes.set(process.id, process);
+
+ for (let pipe of process.pipes) {
+ if (pipe !== undefined)
+ this.pipes.set(pipe.id, pipe);
+ }
+ },
+
+ cleanupProcess(process) {
+ this.processes.delete(process.id);
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/enigmailprocess_worker_win.js
@@ -0,0 +1,753 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+"use strict";
+
+/* exported Process */
+/* globals BaseProcess, BasePipe, win32 */
+
+/* global importScripts: false */
+
+importScripts("chrome://openpgp/content/modules/enigmailprocess_shared.js",
+ "chrome://openpgp/content/modules/enigmailprocess_shared_win.js",
+ "chrome://openpgp/content/modules/enigmailprocess_worker_common.js");
+
+/* global ctypes: false, libc: false, unix: false, SubprocessConstants: false, user32: false */
+ /* global debug: false */
+ /* eslint no-console: 0 */
+
+const POLL_TIMEOUT = 5000;
+
+// The exit code that we send when we forcibly terminate a process.
+const TERMINATE_EXIT_CODE = 0x7f;
+
+let io;
+
+let nextPipeId = 0;
+
+class Pipe extends BasePipe {
+ constructor(process, origHandle) {
+ super();
+
+ let handle = win32.HANDLE();
+
+ let curProc = libc.GetCurrentProcess();
+ libc.DuplicateHandle(curProc, origHandle, curProc, handle.address(),
+ 0, false /* inheritable */ , win32.DUPLICATE_SAME_ACCESS);
+
+ origHandle.dispose();
+
+ this.id = nextPipeId++;
+ this.process = process;
+
+ this.handle = win32.Handle(handle);
+
+ let event = libc.CreateEventW(null, false, false, null);
+
+ this.overlapped = win32.OVERLAPPED();
+ this.overlapped.hEvent = event;
+
+ this._event = win32.Handle(event);
+
+ this.buffer = null;
+ }
+
+ get event() {
+ if (this.pending.length) {
+ return this._event;
+ }
+ return null;
+ }
+
+ maybeClose() {}
+
+ /**
+ * Closes the file handle.
+ *
+ * @param {boolean} [force=false]
+ * If true, the file handle is closed immediately. If false, the
+ * file handle is closed after all current pending IO operations
+ * have completed.
+ *
+ * @returns {Promise<void>}
+ * Resolves when the file handle has been closed.
+ */
+ close(force = false) {
+ if (!force && this.pending.length) {
+ this.closing = true;
+ return this.closedPromise;
+ }
+
+ for (let {reject} of this.pending) {
+ let error = new Error("File closed");
+ error.errorCode = SubprocessConstants.ERROR_END_OF_FILE;
+ reject(error);
+ }
+ this.pending.length = 0;
+
+ this.buffer = null;
+
+ if (!this.closed) {
+ this.handle.dispose();
+ this._event.dispose();
+
+ io.pipes.delete(this.id);
+
+ this.handle = null;
+ this.closed = true;
+ this.resolveClosed();
+
+ io.updatePollEvents();
+ }
+ return this.closedPromise;
+ }
+
+ /**
+ * Called when an error occurred while attempting an IO operation on our file
+ * handle.
+ */
+ onError() {
+ this.close(true);
+ }
+}
+
+class InputPipe extends Pipe {
+ /**
+ * Queues the next chunk of data to be read from the pipe if, and only if,
+ * there is no IO operation currently pending.
+ */
+ readNext() {
+ if (this.buffer === null) {
+ this.readBuffer(this.pending[0].length);
+ }
+ }
+
+ /**
+ * Closes the pipe if there is a pending read operation with no more
+ * buffered data to be read.
+ */
+ maybeClose() {
+ if (this.buffer) {
+ let read = win32.DWORD();
+
+ let ok = libc.GetOverlappedResult(
+ this.handle, this.overlapped.address(),
+ read.address(), false);
+
+ if (!ok) {
+ this.onError();
+ }
+ }
+ }
+
+ /**
+ * Asynchronously reads at most `length` bytes of binary data from the file
+ * descriptor into an ArrayBuffer of the same size. Returns a promise which
+ * resolves when the operation is complete.
+ *
+ * @param {integer} length
+ * The number of bytes to read.
+ *
+ * @returns {Promise<ArrayBuffer>}
+ */
+ read(length) {
+ if (this.closing || this.closed) {
+ throw new Error("Attempt to read from closed pipe");
+ }
+
+ return new Promise((resolve, reject) => {
+ this.pending.push({
+ resolve,
+ reject,
+ length
+ });
+ this.readNext();
+ });
+ }
+
+ /**
+ * Initializes an overlapped IO read operation to read exactly `count` bytes
+ * into a new ArrayBuffer, which is stored in the `buffer` property until the
+ * operation completes.
+ *
+ * @param {integer} count
+ * The number of bytes to read.
+ */
+ readBuffer(count) {
+ this.buffer = new ArrayBuffer(count);
+
+ let ok = libc.ReadFile(this.handle, this.buffer, count,
+ null, this.overlapped.address());
+
+ if (!ok && (!this.process.handle || libc.winLastError)) {
+ this.onError();
+ }
+ else {
+ io.updatePollEvents();
+ }
+ }
+
+ /**
+ * Called when our pending overlapped IO operation has completed, whether
+ * successfully or in failure.
+ */
+ onReady() {
+ let read = win32.DWORD();
+
+ let ok = libc.GetOverlappedResult(
+ this.handle, this.overlapped.address(),
+ read.address(), false);
+
+ read = read.value;
+
+ if (!ok) {
+ this.onError();
+ }
+ else if (read > 0) {
+ let buffer = this.buffer;
+ this.buffer = null;
+
+ let {resolve} = this.shiftPending();
+
+ if (read == buffer.byteLength) {
+ resolve(buffer);
+ }
+ else {
+ resolve(ArrayBuffer.transfer(buffer, read));
+ }
+
+ if (this.pending.length) {
+ this.readNext();
+ }
+ else {
+ io.updatePollEvents();
+ }
+ }
+ }
+}
+
+class OutputPipe extends Pipe {
+ /**
+ * Queues the next chunk of data to be written to the pipe if, and only if,
+ * there is no IO operation currently pending.
+ */
+ writeNext() {
+ if (this.buffer === null) {
+ this.writeBuffer(this.pending[0].buffer);
+ }
+ }
+
+ /**
+ * Asynchronously writes the given buffer to our file descriptor, and returns
+ * a promise which resolves when the operation is complete.
+ *
+ * @param {ArrayBuffer} buffer
+ * The buffer to write.
+ *
+ * @returns {Promise<integer>}
+ * Resolves to the number of bytes written when the operation is
+ * complete.
+ */
+ write(buffer) {
+ if (this.closing || this.closed) {
+ throw new Error("Attempt to write to closed pipe");
+ }
+
+ return new Promise((resolve, reject) => {
+ this.pending.push({
+ resolve,
+ reject,
+ buffer
+ });
+ this.writeNext();
+ });
+ }
+
+ /**
+ * Initializes an overapped IO read operation to write the data in `buffer` to
+ * our file descriptor.
+ *
+ * @param {ArrayBuffer} buffer
+ * The buffer to write.
+ */
+ writeBuffer(buffer) {
+ this.buffer = buffer;
+
+ let ok = libc.WriteFile(this.handle, buffer, buffer.byteLength,
+ null, this.overlapped.address());
+
+ if (!ok && libc.winLastError) {
+ this.onError();
+ }
+ else {
+ io.updatePollEvents();
+ }
+ }
+
+ /**
+ * Called when our pending overlapped IO operation has completed, whether
+ * successfully or in failure.
+ */
+ onReady() {
+ let written = win32.DWORD();
+
+ let ok = libc.GetOverlappedResult(
+ this.handle, this.overlapped.address(),
+ written.address(), false);
+
+ written = written.value;
+
+ if (!ok || written != this.buffer.byteLength) {
+ this.onError();
+ }
+ else if (written > 0) {
+ let {resolve} = this.shiftPending();
+
+ this.buffer = null;
+ resolve(written);
+
+ if (this.pending.length) {
+ this.writeNext();
+ }
+ else {
+ io.updatePollEvents();
+ }
+ }
+ }
+}
+
+class Signal {
+ constructor(event) {
+ this.event = event;
+ }
+
+ cleanup() {
+ libc.CloseHandle(this.event);
+ this.event = null;
+ }
+
+ onError() {
+ io.shutdown();
+ }
+
+ onReady() {
+ io.messageCount += 1;
+ }
+}
+
+class Process extends BaseProcess {
+ constructor(...args) {
+ super(...args);
+
+ this.killed = false;
+ }
+
+ /**
+ * Returns our process handle for use as an event in a WaitForMultipleObjects
+ * call.
+ */
+ get event() {
+ return this.handle;
+ }
+
+ /**
+ * Forcibly terminates the process and all children.
+ */
+ kill() {
+ this.killed = true;
+ libc.TerminateJobObject(this.jobHandle, TERMINATE_EXIT_CODE);
+ }
+
+ /**
+ * Initializes the IO pipes for use as standard input, output, and error
+ * descriptors in the spawned process.
+ *
+ * @returns {win32.Handle[]}
+ * The array of file handles belonging to the spawned process.
+ */
+ initPipes({stderr}) {
+ let our_pipes = [];
+ let their_pipes = [];
+
+ let secAttr = new win32.SECURITY_ATTRIBUTES();
+ secAttr.nLength = win32.SECURITY_ATTRIBUTES.size;
+ secAttr.bInheritHandle = true;
+
+ let pipe = input => {
+ if (input) {
+ let handles = win32.createPipe(secAttr, win32.FILE_FLAG_OVERLAPPED);
+ our_pipes.push(new InputPipe(this, handles[0]));
+ return handles[1];
+ }
+ let handles = win32.createPipe(secAttr, 0, win32.FILE_FLAG_OVERLAPPED);
+ our_pipes.push(new OutputPipe(this, handles[1]));
+ return handles[0];
+ };
+
+ their_pipes[0] = pipe(false);
+ their_pipes[1] = pipe(true);
+
+ if (stderr == "pipe") {
+ their_pipes[2] = pipe(true);
+ }
+ else {
+ let srcHandle;
+ if (stderr == "stdout") {
+ srcHandle = their_pipes[1];
+ }
+ else {
+ srcHandle = libc.GetStdHandle(win32.STD_ERROR_HANDLE);
+ }
+
+ // If we don't have a valid stderr handle, just pass it along without duplicating.
+ if (String(srcHandle) == win32.INVALID_HANDLE_VALUE ||
+ String(srcHandle) == win32.NULL_HANDLE_VALUE) {
+ their_pipes[2] = srcHandle;
+ }
+ else {
+
+ let handle = win32.HANDLE();
+
+ let curProc = libc.GetCurrentProcess();
+ let ok = libc.DuplicateHandle(curProc, srcHandle, curProc, handle.address(),
+ 0, true /* inheritable */ ,
+ win32.DUPLICATE_SAME_ACCESS);
+
+ their_pipes[2] = ok && win32.Handle(handle);
+ }
+ }
+
+ if (!their_pipes.every(handle => handle)) {
+ throw new Error("Failed to create pipe");
+ }
+
+ this.pipes = our_pipes;
+
+ return their_pipes;
+ }
+
+ /**
+ * Creates a null-separated, null-terminated string list.
+ *
+ * @param {Array<string>} strings
+ * @returns {win32.WCHAR.array}
+ */
+ stringList(strings) {
+ // Remove empty strings, which would terminate the list early.
+ strings = strings.filter(string => string);
+
+ let string = strings.join("\0") + "\0\0";
+
+ return win32.WCHAR.array()(string);
+ }
+
+ /**
+ * Quotes a string for use as a single command argument, using Windows quoting
+ * conventions.
+ *
+ * @see https://msdn.microsoft.com/en-us/library/17w5ykft(v=vs.85).aspx
+ *
+ * @param {string} str
+ * The argument string to quote.
+ * @returns {string}
+ */
+ quoteString(str) {
+ if (!/[\s"]/.test(str)) {
+ return str;
+ }
+
+ let escaped = str.replace(/(\\*)("|$)/g, (m0, m1, m2) => {
+ if (m2) {
+ m2 = `${m2}`;
+ }
+ return `${m1}${m1}${m2}`;
+ });
+
+ return `"${escaped}"`;
+ }
+
+ spawn(options) {
+ let {command, arguments: args} = options;
+
+ if (/\\cmd\.exe$/i.test(command) && args.length == 3 && /^(\/S)?\/C$/i.test(args[1])) {
+ // cmd.exe is insane and requires special treatment.
+ args = [this.quoteString(args[0]), "/S/C", `"${args[2]}"`];
+ }
+ else {
+ args = args.map(arg => this.quoteString(arg));
+ }
+
+ if (/\.(bat|cmd)$/i.test(command)) {
+ command = io.comspec;
+ args = ["cmd.exe", "/s/c", `"${args.join(" ")}"`];
+ }
+
+ let envp = this.stringList(options.environment);
+
+ let handles = this.initPipes(options);
+
+ let processFlags = win32.CREATE_NO_WINDOW | win32.CREATE_SUSPENDED | win32.CREATE_UNICODE_ENVIRONMENT;
+
+ if (io.breakAwayFromJob) {
+ processFlags |= win32.CREATE_BREAKAWAY_FROM_JOB;
+ }
+
+ let startupInfoEx = new win32.STARTUPINFOEXW();
+ let startupInfo = startupInfoEx.StartupInfo;
+
+ startupInfo.cb = win32.STARTUPINFOW.size;
+ startupInfo.dwFlags = win32.STARTF_USESTDHANDLES;
+
+ startupInfo.hStdInput = handles[0];
+ startupInfo.hStdOutput = handles[1];
+ startupInfo.hStdError = handles[2];
+
+ // Note: This needs to be kept alive until we destroy the attribute list.
+ let handleArray = win32.HANDLE.array()(handles);
+
+ let threadAttrs = win32.createThreadAttributeList(handleArray);
+ if (threadAttrs) {
+ // If have thread attributes to pass, pass the size of the full extended
+ // startup info struct.
+ processFlags |= win32.EXTENDED_STARTUPINFO_PRESENT;
+ startupInfo.cb = win32.STARTUPINFOEXW.size;
+
+ startupInfoEx.lpAttributeList = threadAttrs;
+ }
+
+ let procInfo = new win32.PROCESS_INFORMATION();
+
+ let errorMessage = "Failed to create process";
+ let ok = libc.CreateProcessW(
+ command, args.join(" "),
+ null, /* Security attributes */
+ null, /* Thread security attributes */
+ true, /* Inherits handles */
+ processFlags, envp, options.workdir,
+ startupInfo.address(),
+ procInfo.address());
+
+ for (let handle of new Set(handles)) {
+ // If any of our handles are invalid, they don't have finalizers.
+ if (handle && handle.dispose) {
+ handle.dispose();
+ }
+ }
+
+ if (threadAttrs) {
+ libc.DeleteProcThreadAttributeList(threadAttrs);
+ }
+
+ if (ok) {
+ this.jobHandle = win32.Handle(libc.CreateJobObjectW(null, null));
+
+ let info = win32.JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
+ info.BasicLimitInformation.LimitFlags = win32.JOB_OBJECT_LIMIT_BREAKAWAY_OK;
+
+ ok = libc.SetInformationJobObject(this.jobHandle, win32.JobObjectExtendedLimitInformation,
+ ctypes.cast(info.address(), ctypes.voidptr_t),
+ info.constructor.size);
+ errorMessage = `Failed to set job limits: 0x${(ctypes.winLastError || 0).toString(16)}`;
+ }
+
+ if (ok) {
+ ok = libc.AssignProcessToJobObject(this.jobHandle, procInfo.hProcess);
+ if (!ok) {
+ errorMessage = `Failed to attach process to job object: 0x${(ctypes.winLastError || 0).toString(16)}`;
+ libc.TerminateProcess(procInfo.hProcess, TERMINATE_EXIT_CODE);
+ }
+ }
+
+ if (!ok) {
+ for (let pipe of this.pipes) {
+ pipe.close();
+ }
+ throw new Error(errorMessage);
+ }
+
+ // Allow child processes to obtain focus via Alt-Tab
+ try {
+ user32.AllowSetForegroundWindow(procInfo.dwProcessId);
+ } catch (x) {}
+
+ this.handle = win32.Handle(procInfo.hProcess);
+ this.pid = procInfo.dwProcessId;
+
+ libc.ResumeThread(procInfo.hThread);
+ libc.CloseHandle(procInfo.hThread);
+ }
+
+ /**
+ * Called when our process handle is signaled as active, meaning the process
+ * has exited.
+ */
+ onReady() {
+ this.wait();
+ }
+
+ /**
+ * Attempts to wait for the process's exit status, without blocking. If
+ * successful, resolves the `exitPromise` to the process's exit value.
+ *
+ * @returns {integer|null}
+ * The process's exit status, if it has already exited.
+ */
+ wait() {
+ if (this.exitCode !== null) {
+ return this.exitCode;
+ }
+
+ let status = win32.DWORD();
+
+ let ok = libc.GetExitCodeProcess(this.handle, status.address());
+ if (ok && status.value != win32.STILL_ACTIVE) {
+ let exitCode = status.value;
+ if (this.killed && exitCode == TERMINATE_EXIT_CODE) {
+ // If we forcibly terminated the process, return the force kill exit
+ // code that we return on other platforms.
+ exitCode = -9;
+ }
+
+ this.resolveExit(exitCode);
+ this.exitCode = exitCode;
+
+ this.handle.dispose();
+ this.handle = null;
+
+ //libc.TerminateJobObject(this.jobHandle, TERMINATE_EXIT_CODE);
+ this.jobHandle.dispose();
+ this.jobHandle = null;
+
+ for (let pipe of this.pipes) {
+ pipe.maybeClose();
+ }
+
+ io.updatePollEvents();
+
+ return exitCode;
+ }
+ else {
+ return null;
+ }
+ }
+}
+
+io = {
+ events: null,
+ eventHandlers: null,
+
+ pipes: new Map(),
+
+ processes: new Map(),
+
+ messageCount: 0,
+
+ running: true,
+
+ init(details) {
+ this.comspec = details.comspec;
+
+ let signalEvent = ctypes.cast(ctypes.uintptr_t(details.signalEvent),
+ win32.HANDLE);
+ this.signal = new Signal(signalEvent);
+ this.updatePollEvents();
+
+ this.breakAwayFromJob = details.breakAwayFromJob;
+
+ setTimeout(this.loop.bind(this), 0);
+ },
+
+ shutdown() {
+ if (this.running) {
+ this.running = false;
+
+ this.signal.cleanup();
+ this.signal = null;
+
+ self.postMessage({
+ msg: "close"
+ });
+ self.close();
+ }
+ },
+
+ getPipe(pipeId) {
+ let pipe = this.pipes.get(pipeId);
+
+ if (!pipe) {
+ let error = new Error("File closed");
+ error.errorCode = SubprocessConstants.ERROR_END_OF_FILE;
+ throw error;
+ }
+ return pipe;
+ },
+
+ getProcess(processId) {
+ let process = this.processes.get(processId);
+
+ if (!process) {
+ throw new Error(`Invalid process ID: ${processId}`);
+ }
+ return process;
+ },
+
+ updatePollEvents() {
+ let handlers = [this.signal,
+ ...this.pipes.values(),
+ ...this.processes.values()
+ ];
+
+ handlers = handlers.filter(handler => handler.event);
+
+ this.eventHandlers = handlers;
+
+ let handles = handlers.map(handler => handler.event);
+ this.events = win32.HANDLE.array()(handles);
+ },
+
+ loop() {
+ this.poll();
+ if (this.running) {
+ setTimeout(this.loop.bind(this), 0);
+ }
+ },
+
+
+ poll() {
+ let timeout = this.messageCount > 0 ? 0 : POLL_TIMEOUT;
+ for (;; timeout = 0) {
+ let events = this.events;
+ let handlers = this.eventHandlers;
+
+ let result = libc.WaitForMultipleObjects(events.length, events,
+ false, timeout);
+
+ if (result < handlers.length) {
+ try {
+ handlers[result].onReady();
+ } catch (e) {
+ console.error(e);
+ debug(`Worker error: ${e} :: ${e.stack}`);
+ handlers[result].onError();
+ }
+ }
+ else {
+ break;
+ }
+ }
+ },
+
+ addProcess(process) {
+ this.processes.set(process.id, process);
+
+ for (let pipe of process.pipes) {
+ this.pipes.set(pipe.id, pipe);
+ }
+ },
+
+ cleanupProcess(process) {
+ this.processes.delete(process.id);
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/errorHandling.jsm
@@ -0,0 +1,642 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailErrorHandling"];
+
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
+const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
+const EnigmailSystem = ChromeUtils.import("chrome://openpgp/content/modules/system.jsm").EnigmailSystem;
+const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
+const EnigmailLazy = ChromeUtils.import("chrome://openpgp/content/modules/lazy.jsm").EnigmailLazy;
+
+const getEnigmailKeyRing = EnigmailLazy.loader("enigmail/keyRing.jsm", "EnigmailKeyRing");
+const getEnigmailGpg = EnigmailLazy.loader("enigmail/gpg.jsm", "EnigmailGpg");
+const getEnigmailFiles = EnigmailLazy.loader("enigmail/files.jsm", "EnigmailFiles");
+const getEnigmailRNG = EnigmailLazy.loader("enigmail/rng.jsm", "EnigmailRNG");
+
+
+const gStatusFlags = {
+ GOODSIG: EnigmailConstants.GOOD_SIGNATURE,
+ BADSIG: EnigmailConstants.BAD_SIGNATURE,
+ ERRSIG: EnigmailConstants.UNVERIFIED_SIGNATURE,
+ EXPSIG: EnigmailConstants.EXPIRED_SIGNATURE,
+ REVKEYSIG: EnigmailConstants.GOOD_SIGNATURE,
+ EXPKEYSIG: EnigmailConstants.EXPIRED_KEY_SIGNATURE,
+ KEYEXPIRED: EnigmailConstants.EXPIRED_KEY,
+ KEYREVOKED: EnigmailConstants.REVOKED_KEY,
+ NO_PUBKEY: EnigmailConstants.NO_PUBKEY,
+ NO_SECKEY: EnigmailConstants.NO_SECKEY,
+ IMPORTED: EnigmailConstants.IMPORTED_KEY,
+ INV_RECP: EnigmailConstants.INVALID_RECIPIENT,
+ MISSING_PASSPHRASE: EnigmailConstants.MISSING_PASSPHRASE,
+ BAD_PASSPHRASE: EnigmailConstants.BAD_PASSPHRASE,
+ BADARMOR: EnigmailConstants.BAD_ARMOR,
+ NODATA: EnigmailConstants.NODATA,
+ ERROR: EnigmailConstants.BAD_SIGNATURE | EnigmailConstants.DECRYPTION_FAILED,
+ DECRYPTION_FAILED: EnigmailConstants.DECRYPTION_FAILED,
+ DECRYPTION_OKAY: EnigmailConstants.DECRYPTION_OKAY,
+ CARDCTRL: EnigmailConstants.CARDCTRL,
+ SC_OP_FAILURE: EnigmailConstants.SC_OP_FAILURE,
+ UNKNOWN_ALGO: EnigmailConstants.UNKNOWN_ALGO,
+ SIG_CREATED: EnigmailConstants.SIG_CREATED,
+ END_ENCRYPTION: EnigmailConstants.END_ENCRYPTION,
+ INV_SGNR: 0x100000000,
+ IMPORT_OK: 0x200000000,
+ FAILURE: 0x400000000,
+ DECRYPTION_INFO: 0x800000000
+};
+
+// taken from libgpg-error: gpg-error.h
+const GPG_SOURCE_SYSTEM = {
+ GPG_ERR_SOURCE_UNKNOWN: 0,
+ GPG_ERR_SOURCE_GCRYPT: 1,
+ GPG_ERR_SOURCE_GPG: 2,
+ GPG_ERR_SOURCE_GPGSM: 3,
+ GPG_ERR_SOURCE_GPGAGENT: 4,
+ GPG_ERR_SOURCE_PINENTRY: 5,
+ GPG_ERR_SOURCE_SCD: 6,
+ GPG_ERR_SOURCE_GPGME: 7,
+ GPG_ERR_SOURCE_KEYBOX: 8,
+ GPG_ERR_SOURCE_KSBA: 9,
+ GPG_ERR_SOURCE_DIRMNGR: 10,
+ GPG_ERR_SOURCE_GSTI: 11,
+ GPG_ERR_SOURCE_GPA: 12,
+ GPG_ERR_SOURCE_KLEO: 13,
+ GPG_ERR_SOURCE_G13: 14,
+ GPG_ERR_SOURCE_ASSUAN: 15,
+ GPG_ERR_SOURCE_TLS: 17,
+ GPG_ERR_SOURCE_ANY: 31
+};
+
+/**
+ * Handling of specific error codes from GnuPG
+ *
+ * @param c Object - the retStatusObj
+ * @param errorNumber String - the error number as printed by GnuPG
+ */
+function handleErrorCode(c, errorNumber) {
+ if (errorNumber && errorNumber.search(/^[0-9]+$/) === 0) {
+ let errNum = Number(errorNumber);
+ let sourceSystem = errNum >> 24;
+ let errorCode = errNum & 0xFFFFFF;
+
+ switch (errorCode) {
+ case 32870: // error no tty
+ if (sourceSystem === GPG_SOURCE_SYSTEM.GPG_ERR_SOURCE_PINENTRY) {
+ c.statusFlags |= EnigmailConstants.DISPLAY_MESSAGE;
+ c.retStatusObj.extendedStatus += "disp:get_passphrase ";
+ c.retStatusObj.statusMsg = EnigmailLocale.getString("errorHandling.pinentryCursesError") + "\n\n" + EnigmailLocale.getString("errorHandling.readFaq");
+ c.isError = true;
+ }
+ break;
+ case 11: // bad Passphrase
+ case 87: // bad PIN
+ badPassphrase(c);
+ break;
+ case 177: // no passphrase
+ case 178: // no PIN
+ missingPassphrase(c);
+ break;
+ case 99: // operation canceled
+ if (sourceSystem === GPG_SOURCE_SYSTEM.GPG_ERR_SOURCE_PINENTRY) {
+ missingPassphrase(c);
+ }
+ break;
+ case 77: // no agent
+ case 78: // agent error
+ case 80: // assuan server fault
+ case 81: // assuan error
+ c.statusFlags |= EnigmailConstants.DISPLAY_MESSAGE;
+ c.retStatusObj.extendedStatus += "disp:get_passphrase ";
+ c.retStatusObj.statusMsg = EnigmailLocale.getString("errorHandling.gpgAgentError") + "\n\n" + EnigmailLocale.getString("errorHandling.readFaq");
+ c.isError = true;
+ break;
+ case 85: // no pinentry
+ case 86: // pinentry error
+ c.statusFlags |= EnigmailConstants.DISPLAY_MESSAGE;
+ c.retStatusObj.extendedStatus += "disp:get_passphrase ";
+ c.retStatusObj.statusMsg = EnigmailLocale.getString("errorHandling.pinentryError") + "\n\n" + EnigmailLocale.getString("errorHandling.readFaq");
+ c.isError = true;
+ break;
+ case 92: // no dirmngr
+ case 93: // dirmngr error
+ c.statusFlags |= EnigmailConstants.DISPLAY_MESSAGE;
+ c.retStatusObj.extendedStatus += "disp:get_passphrase ";
+ c.retStatusObj.statusMsg = EnigmailLocale.getString("errorHandling.dirmngrError") + "\n\n" + EnigmailLocale.getString("errorHandling.readFaq");
+ c.isError = true;
+ break;
+ case 2:
+ case 3:
+ case 149:
+ case 188:
+ c.statusFlags |= EnigmailConstants.UNKNOWN_ALGO;
+ break;
+ case 15:
+ c.statusFlags |= EnigmailConstants.BAD_ARMOR;
+ break;
+ case 58:
+ c.statusFlags |= EnigmailConstants.NODATA;
+ break;
+ }
+ }
+}
+
+/**
+ * Special treatment for some ERROR messages from GnuPG
+ *
+ * extendedStatus are preceeded by "disp:" if an error message is set in statusMsg
+ *
+ * isError is set to true if this is a hard error that makes further processing of
+ * the status codes useless
+ */
+function handleError(c) {
+ /*
+ check_hijacking: gpg-agent was hijacked by some other process (like gnome-keyring)
+ proc_pkt.plaintext: multiple plaintexts seen
+ pkdecrypt_failed: public key decryption failed
+ keyedit.passwd: error changing the passphrase
+ card_key_generate: key generation failed (card)
+ key_generate: key generation failed
+ keyserver_send: keyserver send failed
+ get_passphrase: gpg-agent cannot query the passphrase from pinentry (GnuPG 2.0.x)
+ */
+
+ var lineSplit = c.statusLine.split(/ +/);
+ if (lineSplit.length > 0) {
+
+ if (lineSplit.length >= 3) {
+ // first check if the error code is a specifically treated hard failure
+ handleErrorCode(c, lineSplit[2]);
+ if (c.isError) return true;
+ }
+
+ switch (lineSplit[1]) {
+ case "check_hijacking":
+ c.statusFlags |= EnigmailConstants.DISPLAY_MESSAGE;
+ c.retStatusObj.extendedStatus += "disp:invalid_gpg_agent ";
+ c.retStatusObj.statusMsg = EnigmailLocale.getString("errorHandling.gpgAgentInvalid") + "\n\n" + EnigmailLocale.getString("errorHandling.readFaq");
+ c.isError = true;
+ break;
+ case "get_passphrase":
+ c.statusFlags |= EnigmailConstants.DISPLAY_MESSAGE;
+ c.retStatusObj.extendedStatus += "disp:get_passphrase ";
+ c.retStatusObj.statusMsg = EnigmailLocale.getString("errorHandling.pinentryError") + "\n\n" + EnigmailLocale.getString("errorHandling.readFaq");
+ c.isError = true;
+ break;
+ case "proc_pkt.plaintext":
+ c.retStatusObj.extendedStatus += "multiple_plaintexts ";
+ c.isError = true;
+ break;
+ case "pkdecrypt_failed":
+ c.retStatusObj.extendedStatus += "pubkey_decrypt ";
+ handleErrorCode(c, lineSplit[2]);
+ break;
+ case "keyedit.passwd":
+ c.retStatusObj.extendedStatus += "passwd_change_failed ";
+ break;
+ case "card_key_generate":
+ case "key_generate":
+ c.retStatusObj.extendedStatus += "key_generate_failure ";
+ break;
+ case "keyserver_send":
+ c.retStatusObj.extendedStatus += "keyserver_send_failed ";
+ c.isError = true;
+ break;
+ default:
+ return false;
+ }
+ return true;
+ } else {
+ return false;
+ }
+}
+
+// handle GnuPG FAILURE message (GnuPG 2.1.10 and newer)
+function failureMessage(c) {
+ let lineSplit = c.statusLine.split(/ +/);
+ if (lineSplit.length >= 3) {
+ handleErrorCode(c, lineSplit[2]);
+ }
+}
+
+function missingPassphrase(c) {
+ c.statusFlags |= EnigmailConstants.MISSING_PASSPHRASE;
+ if (c.retStatusObj.statusMsg.indexOf(EnigmailLocale.getString("missingPassphrase")) < 0) {
+ c.statusFlags |= EnigmailConstants.DISPLAY_MESSAGE;
+ c.flag = 0;
+ EnigmailLog.DEBUG("errorHandling.jsm: missingPassphrase: missing passphrase\n");
+ c.retStatusObj.statusMsg += EnigmailLocale.getString("missingPassphrase") + "\n";
+ }
+}
+
+function badPassphrase(c) {
+ c.statusFlags |= EnigmailConstants.MISSING_PASSPHRASE;
+ if (!(c.statusFlags & EnigmailConstants.BAD_PASSPHRASE)) {
+ c.statusFlags |= EnigmailConstants.BAD_PASSPHRASE;
+ c.flag = 0;
+ EnigmailLog.DEBUG("errorHandling.jsm: badPassphrase: bad passphrase\n");
+ c.retStatusObj.statusMsg += EnigmailLocale.getString("badPhrase") + "\n";
+ }
+}
+
+
+function invalidSignature(c) {
+ if (c.isError) return;
+ var lineSplit = c.statusLine.split(/ +/);
+ c.statusFlags |= EnigmailConstants.DISPLAY_MESSAGE;
+ c.flag = 0;
+
+ let keySpec = lineSplit[2];
+
+ if (keySpec) {
+ EnigmailLog.DEBUG("errorHandling.jsm: invalidRecipient: detected invalid sender " + keySpec + " / code: " + lineSplit[1] + "\n");
+ c.retStatusObj.errorMsg += EnigmailErrorHandling.determineInvSignReason(keySpec);
+ }
+}
+
+function invalidRecipient(c) {
+ if (c.isError) return;
+ var lineSplit = c.statusLine.split(/ +/);
+ c.statusFlags |= EnigmailConstants.DISPLAY_MESSAGE;
+ c.flag = 0;
+
+ let keySpec = lineSplit[2];
+
+ if (keySpec) {
+ EnigmailLog.DEBUG("errorHandling.jsm: invalidRecipient: detected invalid recipient " + keySpec + " / code: " + lineSplit[1] + "\n");
+ c.retStatusObj.errorMsg += EnigmailErrorHandling.determineInvRcptReason(keySpec);
+ }
+}
+
+function importOk(c) {
+ var lineSplit = c.statusLine.split(/ +/);
+ if (lineSplit.length > 1) {
+ EnigmailLog.DEBUG("errorHandling.jsm: importOk: key imported: " + lineSplit[2] + "\n");
+ } else {
+ EnigmailLog.DEBUG("errorHandling.jsm: importOk: key without FPR imported\n");
+ }
+}
+
+function unverifiedSignature(c) {
+ var lineSplit = c.statusLine.split(/ +/);
+ if (lineSplit.length > 7 && lineSplit[7] == "4") {
+ c.flag = EnigmailConstants.UNKNOWN_ALGO;
+ }
+}
+
+function noData(c) {
+ // Recognize only "NODATA 1"
+ if (c.statusLine.search(/NODATA 1\b/) < 0) {
+ c.flag = 0;
+ }
+}
+
+function decryptionInfo(c) {
+ // Recognize "DECRYPTION_INFO 0 1 2"
+ if (c.statusLine.search(/DECRYPTION_INFO /) >= 0) {
+ let lineSplit = c.statusLine.split(/ +/);
+
+ let mdcMethod = lineSplit[1];
+ let aeadAlgo = lineSplit.length > 3 ? lineSplit[3] : "0";
+
+ if (mdcMethod === "0" && aeadAlgo === "0") {
+ c.statusFlags |= EnigmailConstants.MISSING_MDC;
+ c.statusFlags |= EnigmailConstants.DECRYPTION_FAILED; // be sure to fail
+ c.flag = EnigmailConstants.MISSING_MDC;
+ EnigmailLog.DEBUG("errorHandling.jsm: missing MDC!\n");
+ c.retStatusObj.statusMsg += EnigmailLocale.getString("missingMdcError") + "\n";
+ }
+ }
+}
+
+
+function decryptionFailed(c) {
+ c.inDecryptionFailed = true;
+}
+
+function cardControl(c) {
+ var lineSplit = c.statusLine.split(/ +/);
+ if (lineSplit[1] == "3") {
+ c.detectedCard = lineSplit[2];
+ } else {
+ c.errCode = Number(lineSplit[1]);
+ if (c.errCode == 1) c.requestedCard = lineSplit[2];
+ }
+}
+
+function setupFailureLookup() {
+ var result = {};
+ result[EnigmailConstants.DECRYPTION_FAILED] = decryptionFailed;
+ result[EnigmailConstants.NODATA] = noData;
+ result[EnigmailConstants.CARDCTRL] = cardControl;
+ result[EnigmailConstants.UNVERIFIED_SIGNATURE] = unverifiedSignature;
+ result[EnigmailConstants.MISSING_PASSPHRASE] = missingPassphrase;
+ result[EnigmailConstants.BAD_PASSPHRASE] = badPassphrase;
+ result[gStatusFlags.INV_RECP] = invalidRecipient;
+ result[gStatusFlags.INV_SGNR] = invalidSignature;
+ result[gStatusFlags.IMPORT_OK] = importOk;
+ result[gStatusFlags.FAILURE] = failureMessage;
+ result[gStatusFlags.DECRYPTION_INFO] = decryptionInfo;
+ return result;
+}
+
+function ignore() {}
+
+const failureLookup = setupFailureLookup();
+
+function handleFailure(c, errorFlag) {
+ c.flag = gStatusFlags[errorFlag]; // yields known flag or undefined
+
+ (failureLookup[c.flag] || ignore)(c);
+
+ // if known flag, story it in our status
+ if (c.flag) {
+ c.statusFlags |= c.flag;
+ }
+}
+
+function newContext(errOutput, retStatusObj) {
+ retStatusObj.statusMsg = "";
+ retStatusObj.errorMsg = "";
+ retStatusObj.extendedStatus = "";
+ retStatusObj.blockSeparation = "";
+ retStatusObj.encryptedFileName = null;
+
+ return {
+ errOutput: errOutput,
+ retStatusObj: retStatusObj,
+ errArray: [],
+ statusArray: [],
+ errCode: 0,
+ detectedCard: null,
+ requestedCard: null,
+ errorMsg: "",
+ statusPat: /^\[GNUPG:\] /,
+ statusFlags: 0,
+ plaintextCount: 0,
+ withinCryptoMsg: false,
+ cryptoStartPat: /^BEGIN_DECRYPTION/,
+ cryptoEndPat: /^END_DECRYPTION/,
+ plaintextPat: /^PLAINTEXT /,
+ plaintextLengthPat: /^PLAINTEXT_LENGTH /
+ };
+}
+
+function splitErrorOutput(errOutput) {
+ var errLines = errOutput.split(/\r?\n/);
+
+ // Discard last null string, if any
+ if ((errLines.length > 1) && !errLines[errLines.length - 1]) {
+ errLines.pop();
+ }
+
+ return errLines;
+}
+
+function parseErrorLine(errLine, c) {
+ if (errLine.search(c.statusPat) === 0) {
+ // status line
+ c.statusLine = errLine.replace(c.statusPat, "");
+ c.statusArray.push(c.statusLine);
+
+ // extract first word as flag
+ var matches = c.statusLine.match(/^((\w+)\b)/);
+
+ if (matches && (matches.length > 1)) {
+ let isError = (matches[1] == "ERROR");
+ (isError ? handleError : handleFailure)(c, matches[1]);
+ }
+ } else {
+ // non-status line (details of previous status command)
+ if (!getEnigmailGpg().getGpgFeature("decryption-info")) {
+ if (errLine == "gpg: WARNING: message was not integrity protected") {
+ // workaround for Gpg < 2.0.19 that don't print DECRYPTION_INFO
+ c.statusFlags |= EnigmailConstants.DECRYPTION_FAILED;
+ c.inDecryptionFailed = true;
+ }
+ }
+
+ c.errArray.push(errLine);
+ // save details of DECRYPTION_FAILED message ass error message
+ if (c.inDecryptionFailed) {
+ c.errorMsg += errLine;
+ }
+ }
+}
+
+function detectForgedInsets(c) {
+ // detect forged message insets
+ let hasUnencryptedText = false;
+ let hasEncryptedPart = false;
+ for (var j = 0; j < c.statusArray.length; j++) {
+ if (c.statusArray[j].search(c.cryptoStartPat) === 0) {
+ c.withinCryptoMsg = true;
+ hasEncryptedPart = true;
+ } else if (c.withinCryptoMsg && c.statusArray[j].search(c.cryptoEndPat) === 0) {
+ c.withinCryptoMsg = false;
+ } else if (c.statusArray[j].search(c.plaintextPat) === 0) {
+ if (!c.withinCryptoMsg) hasUnencryptedText = true;
+
+ ++c.plaintextCount;
+ if ((c.statusArray.length > j + 1) && (c.statusArray[j + 1].search(c.plaintextLengthPat) === 0)) {
+ var matches = c.statusArray[j + 1].match(/(\w+) (\d+)/);
+ if (matches.length >= 3) {
+ c.retStatusObj.blockSeparation += (c.withinCryptoMsg ? "1" : "0") + ":" + matches[2] + " ";
+ }
+ } else {
+ // strange: we got PLAINTEXT XX, but not PLAINTEXT_LENGTH XX
+ c.retStatusObj.blockSeparation += (c.withinCryptoMsg ? "1" : "0") + ":0 ";
+ }
+ }
+ }
+ if (c.plaintextCount > 1 || (hasEncryptedPart && hasUnencryptedText)) {
+ c.statusFlags |= (EnigmailConstants.DECRYPTION_FAILED | EnigmailConstants.BAD_SIGNATURE);
+ }
+}
+
+function buildErrorMessageForCardCtrl(c, errCode, detectedCard) {
+ var errorMsg = "";
+ switch (errCode) {
+ case 1:
+ if (detectedCard) {
+ errorMsg = EnigmailLocale.getString("sc.wrongCardAvailable", [c.detectedCard, c.requestedCard]);
+ } else {
+ errorMsg = EnigmailLocale.getString("sc.insertCard", [c.requestedCard]);
+ }
+ break;
+ case 2:
+ errorMsg = EnigmailLocale.getString("sc.removeCard");
+ break;
+ case 4:
+ errorMsg = EnigmailLocale.getString("sc.noCardAvailable");
+ break;
+ case 5:
+ errorMsg = EnigmailLocale.getString("sc.noReaderAvailable");
+ break;
+ }
+ return errorMsg;
+}
+
+function parseErrorOutputWith(c) {
+ EnigmailLog.DEBUG("errorHandling.jsm: parseErrorOutputWith: status message: \n" + c.errOutput + "\n");
+
+ c.errLines = splitErrorOutput(c.errOutput);
+ c.isError = false; // set to true if a hard error was found
+
+ // parse all error lines
+ c.inDecryptionFailed = false; // to save details of encryption failed messages
+ for (var j = 0; j < c.errLines.length; j++) {
+ var errLine = c.errLines[j];
+ parseErrorLine(errLine, c);
+ if (c.isError) break;
+ }
+
+ detectForgedInsets(c);
+
+ c.retStatusObj.blockSeparation = c.retStatusObj.blockSeparation.replace(/ $/, "");
+ c.retStatusObj.statusFlags = c.statusFlags;
+ if (c.retStatusObj.statusMsg.length === 0) c.retStatusObj.statusMsg = c.statusArray.join("\n");
+ if (c.errorMsg.length === 0) {
+ c.errorMsg = c.errArray.map(function f(str, idx) {
+ return EnigmailSystem.convertNativeToUnicode(str);
+ }, EnigmailSystem).join("\n");
+ } else {
+ c.errorMsg = EnigmailSystem.convertNativeToUnicode(c.errorMsg);
+ }
+
+ if ((c.statusFlags & EnigmailConstants.CARDCTRL) && c.errCode > 0) {
+ c.errorMsg = buildErrorMessageForCardCtrl(c, c.errCode, c.detectedCard);
+ c.statusFlags |= EnigmailConstants.DISPLAY_MESSAGE;
+ }
+
+ let inDecryption = 0;
+ let m;
+ for (let i in c.statusArray) {
+ if (c.statusArray[i].search(/^BEGIN_DECRYPTION( .*)?$/) === 0) {
+ inDecryption = 1;
+ } else if (c.statusArray[i].search(/^END_DECRYPTION( .*)?$/) === 0) {
+ inDecryption = 0;
+ } else if (inDecryption >0) {
+ m = c.statusArray[i].match(/^(PLAINTEXT [0-9]+ [0-9]+ )(.*)$/);
+ if (m && m.length >= 3) c.retStatusObj.encryptedFileName = m[2];
+ }
+ }
+
+ EnigmailLog.DEBUG("errorHandling.jsm: parseErrorOutputWith: statusFlags = " + EnigmailData.bytesToHex(EnigmailData.pack(c.statusFlags, 4)) + "\n");
+ EnigmailLog.DEBUG("errorHandling.jsm: parseErrorOutputWith: return with c.errorMsg = " + c.errorMsg + "\n");
+ return c.errorMsg;
+}
+
+var EnigmailErrorHandling = {
+ parseErrorOutput: function(errOutput, retStatusObj) {
+ var context = newContext(errOutput, retStatusObj);
+ return parseErrorOutputWith(context);
+ },
+
+ /**
+ * Determin why a given key or userID cannot be used for signing
+ *
+ * @param keySpec String - key ID or user ID
+ *
+ * @return String - the reason(s) as message to display to the user
+ * "" in case the key is valid
+ */
+ determineInvSignReason: function(keySpec) {
+ EnigmailLog.DEBUG("errorHandling.jsm: determineInvSignReason: keySpec: " + keySpec + "\n");
+
+ let reasonMsg = "";
+
+ if (keySpec.search(/^(0x)?[0-9A-F]+$/) === 0) {
+ let key = getEnigmailKeyRing().getKeyById(keySpec);
+ if (!key) {
+ reasonMsg = EnigmailLocale.getString("keyError.keyIdNotFound", keySpec);
+ } else {
+ let r = key.getSigningValidity();
+ if (!r.keyValid) reasonMsg = r.reason;
+ }
+ } else {
+ let keys = getEnigmailKeyRing().getKeysByUserId(keySpec);
+ if (!keys || keys.length === 0) {
+ reasonMsg = EnigmailLocale.getString("keyError.keySpecNotFound", keySpec);
+ } else {
+ for (let i in keys) {
+ let r = keys[i].getSigningValidity();
+ if (!r.keyValid) reasonMsg += r.reason + "\n";
+ }
+ }
+ }
+
+ return reasonMsg;
+ },
+
+ /**
+ * Determin why a given key or userID cannot be used for encryption
+ *
+ * @param keySpec String - key ID or user ID
+ *
+ * @return String - the reason(s) as message to display to the user
+ * "" in case the key is valid
+ */
+ determineInvRcptReason: function(keySpec) {
+ EnigmailLog.DEBUG("errorHandling.jsm: determineInvRcptReason: keySpec: " + keySpec + "\n");
+
+ let reasonMsg = "";
+
+ if (keySpec.search(/^(0x)?[0-9A-F]+$/) === 0) {
+ let key = getEnigmailKeyRing().getKeyById(keySpec);
+ if (!key) {
+ reasonMsg = EnigmailLocale.getString("keyError.keyIdNotFound", keySpec);
+ } else {
+ let r = key.getEncryptionValidity();
+ if (!r.keyValid) reasonMsg = r.reason;
+ }
+ } else {
+ let keys = getEnigmailKeyRing().getKeysByUserId(keySpec);
+ if (!keys || keys.length === 0) {
+ reasonMsg = EnigmailLocale.getString("keyError.keySpecNotFound", keySpec);
+ } else {
+ for (let i in keys) {
+ let r = keys[i].getEncryptionValidity();
+ if (!r.keyValid) reasonMsg += r.reason + "\n";
+ }
+ }
+ }
+
+ return reasonMsg;
+ },
+
+ /**
+ * Get a unique file to use for logging with --log-file
+ */
+ getTempLogFile: function() {
+ let logFile = getEnigmailFiles().getTempDirObj().clone();
+ logFile.normalize();
+ logFile.append("gpgOutput." + getEnigmailRNG().generateRandomString(6));
+ return logFile;
+ },
+
+
+ /**
+ * Append the content of a file (such as created via --log-file) to the
+ * debug log, and delete the file afterwards
+ *
+ * @param logFile: nsIFile object
+ */
+ appendLogFileToDebug: function(logFile) {
+ if (logFile && logFile.exists() && logFile.isFile()) {
+ let logData = getEnigmailFiles().readFile(logFile);
+
+ EnigmailLog.DEBUG(`errorHandling.jsm: Process terminated. Human-readable output from gpg:\n-----\n${logData}-----\n`);
+ try {
+ logFile.remove(false);
+ } catch (ex) {}
+ }
+ }
+};
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/events.jsm
@@ -0,0 +1,33 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailEvents"];
+
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailTimer = ChromeUtils.import("chrome://openpgp/content/modules/timer.jsm").EnigmailTimer;
+
+/**** DEPRECATED - use EnigmailTimer instead *****/
+
+var EnigmailEvents = {
+ /**
+ * dispatch event aynchronously to the main thread
+ *
+ * @callbackFunction: Function - any function specification
+ * @sleepTimeMs: Number - optional number of miliseconds to delay
+ * (0 if not specified)
+ * @arrayOfArgs: Array - arguments to pass to callbackFunction
+ */
+ dispatchEvent: function(callbackFunction, sleepTimeMs, arrayOfArgs) {
+ EnigmailLog.DEBUG("enigmailCommon.jsm: dispatchEvent f=" + callbackFunction.name + "\n");
+
+ return EnigmailTimer.setTimeout(() => {
+ callbackFunction(arrayOfArgs);
+ }, sleepTimeMs);
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/execution.jsm
@@ -0,0 +1,525 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+const EXPORTED_SYMBOLS = ["EnigmailExecution"];
+
+const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
+const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const subprocess = ChromeUtils.import("chrome://openpgp/content/modules/subprocess.jsm").subprocess;
+const EnigmailErrorHandling = ChromeUtils.import("chrome://openpgp/content/modules/errorHandling.jsm").EnigmailErrorHandling;
+const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
+const EnigmailLazy = ChromeUtils.import("chrome://openpgp/content/modules/lazy.jsm").EnigmailLazy;
+const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
+
+const loadOS = EnigmailLazy.loader("enigmail/os.jsm", "EnigmailOS");
+
+var EnigmailExecution = {
+ agentType: "",
+
+ /**
+ * execStart Listener Object
+ *
+ * The listener object must implement at least the following methods:
+ *
+ * stdin(pipe) - OPTIONAL - write data to subprocess stdin via |pipe| hanlde
+ * stdout(data) - receive |data| from subprocess stdout
+ * stderr(data) - receive |data| from subprocess stderr
+ * done(exitCode) - receive signal when subprocess has terminated
+ */
+
+ /**
+ * start a subprocess (usually gpg) that gets and/or receives data via stdin/stdout/stderr.
+ *
+ * @param {String/nsIFile} command: either full path to executable
+ * or: object referencing executable
+ * @param {Array of Strings} args: command line parameters for executable
+ * @param {Boolean} needPassphrase: is a passphrase required for the action?
+ * (this is currently a no-op)
+ * @param {nsIWindow} domWindow: window on top of which password dialog is shown
+ * @param {Object} listener: Listener to interact with subprocess; see spec. above
+ * @param {Object} statusflagsObj: .value will hold status Flags
+ *
+ * @return {Object}: handle to subprocess
+ */
+ execStart: function(command, args, needPassphrase, domWindow, listener, statusFlagsObj) {
+ EnigmailLog.WRITE("execution.jsm: execStart: " +
+ "command = " + EnigmailFiles.formatCmdLine(command, args) +
+ ", needPassphrase=" + needPassphrase +
+ ", domWindow=" + domWindow +
+ ", listener=" + listener + "\n");
+
+ listener = listener || {};
+
+ statusFlagsObj.value = 0;
+
+ let proc = null;
+
+ listener.command = command;
+
+ EnigmailLog.CONSOLE("enigmail> " + EnigmailFiles.formatCmdLine(command, args) + "\n");
+
+ try {
+ proc = subprocess.call({
+ command: command,
+ arguments: args,
+ environment: EnigmailCore.getEnvList(),
+ charset: null,
+ bufferedOutput: true,
+ stdin: function(pipe) {
+ if (listener.stdin) listener.stdin(pipe);
+ },
+ stdout: function(data) {
+ listener.stdout(data);
+ },
+ stderr: function(data) {
+ listener.stderr(data);
+ },
+ done: function(result) {
+ try {
+ listener.done(result.exitCode);
+ }
+ catch (ex) {
+ EnigmailLog.writeException("execution.jsm", ex);
+ }
+ },
+ mergeStderr: false
+ });
+ }
+ catch (ex) {
+ EnigmailLog.ERROR("execution.jsm: execStart: subprocess.call failed with '" + ex.toString() + "'\n");
+ EnigmailLog.DEBUG(" enigmail> DONE with FAILURE\n");
+ return null;
+ }
+ EnigmailLog.DEBUG(" enigmail> DONE\n");
+
+ return proc;
+ },
+
+ /*
+ requirements for listener object:
+ exitCode
+ stderrData
+ */
+ execEnd: function(listener, statusFlagsObj, statusMsgObj, cmdLineObj, errorMsgObj, blockSeparationObj) {
+ EnigmailLog.DEBUG("execution.jsm: execEnd:\n");
+
+ cmdLineObj.value = listener.command;
+
+ let exitCode = listener.exitCode;
+ const errOutput = listener.stderrData;
+
+ EnigmailLog.DEBUG("execution.jsm: execEnd: exitCode = " + exitCode + "\n");
+ EnigmailLog.DEBUG("execution.jsm: execEnd: errOutput = " + errOutput.substr(0, 500) + "\n");
+
+ const retObj = {};
+ errorMsgObj.value = EnigmailErrorHandling.parseErrorOutput(errOutput, retObj);
+ statusFlagsObj.value = retObj.statusFlags;
+ statusMsgObj.value = retObj.statusMsg;
+ statusFlagsObj.encryptedFileName = retObj.encryptedFileName;
+ if (!blockSeparationObj) blockSeparationObj = {};
+ blockSeparationObj.value = retObj.blockSeparation;
+
+ if (errOutput.search(/jpeg image of size \d+/) > -1) {
+ statusFlagsObj.value |= EnigmailConstants.PHOTO_AVAILABLE;
+ }
+ if (blockSeparationObj && blockSeparationObj.value.indexOf(" ") > 0) {
+ exitCode = 2;
+ }
+
+ EnigmailLog.CONSOLE(EnigmailData.convertFromUnicode(errorMsgObj.value) + "\n");
+
+ return exitCode;
+ },
+
+ /**
+ * Resolve the path to the command and execute it if available
+ * Returns output from simpleExecCmd
+ */
+ resolveAndSimpleExec: function(command, args, exitCodeObj, errorMsgObj) {
+ const resolvedCommand = EnigmailFiles.resolvePathWithEnv(command);
+ if (resolvedCommand === null) {
+ return null;
+ }
+ return EnigmailExecution.simpleExecCmd(resolvedCommand, args, exitCodeObj, errorMsgObj);
+ },
+
+ /**
+ * Execute a command and return the output from stdout
+ * No input and no statusFlags are returned.
+ */
+ simpleExecCmd: function(command, args, exitCodeObj, errorMsgObj) {
+ EnigmailLog.WRITE("execution.jsm: EnigmailExecution.simpleExecCmd: command = " + command + " " + args.join(" ") + "\n");
+
+ let outputData = "";
+ let errOutput = "";
+ errorMsgObj.value = "";
+ exitCodeObj.value = -1;
+
+ EnigmailLog.CONSOLE("enigmail> " + EnigmailFiles.formatCmdLine(command, args) + "\n");
+
+ try {
+ subprocess.call({
+ command: command,
+ arguments: args,
+ charset: null,
+ environment: EnigmailCore.getEnvList(),
+ done: function(result) {
+ exitCodeObj.value = result.exitCode;
+ outputData = result.stdout;
+ errOutput = result.stderr;
+ },
+ mergeStderr: false
+ }).wait();
+ }
+ catch (ex) {
+ EnigmailLog.ERROR("execution.jsm: EnigmailExecution.simpleExecCmd: " + command.path + " failed\n");
+ EnigmailLog.DEBUG(" enigmail> DONE with FAILURE\n");
+ exitCodeObj.value = -1;
+ }
+ EnigmailLog.DEBUG(" enigmail> DONE\n");
+
+ if (errOutput) {
+ errorMsgObj.value = errOutput;
+ }
+
+ EnigmailLog.DEBUG("execution.jsm: EnigmailExecution.simpleExecCmd: exitCode = " + exitCodeObj.value + "\n");
+ EnigmailLog.DEBUG("execution.jsm: EnigmailExecution.simpleExecCmd: errOutput = " + errOutput.substr(0, 500) + "\n");
+
+ return outputData;
+ },
+
+ /**
+ * Execute a command and return the output from stdout.
+ * Accepts input and returns error message and statusFlags.
+ */
+ execCmd: function(command, args, input, exitCodeObj, statusFlagsObj, statusMsgObj,
+ errorMsgObj, retStatusObj) {
+ EnigmailLog.WRITE("execution.jsm: EnigmailExecution.execCmd: subprocess = '" + command.path + "'\n");
+
+ if ((typeof input) != "string") input = "";
+
+ let preInput = "";
+ let outputData = "";
+ let errOutput = "";
+ EnigmailLog.CONSOLE("enigmail> " + EnigmailFiles.formatCmdLine(command, args) + "\n");
+
+ const procBuilder = new EnigmailExecution.processBuilder();
+ procBuilder.setCommand(command);
+ procBuilder.setArguments(args);
+ procBuilder.setEnvironment(EnigmailCore.getEnvList());
+ procBuilder.setStdin(
+ function(pipe) {
+ if (input.length > 0 || preInput.length > 0) {
+ pipe.write(preInput + input);
+ }
+ pipe.close();
+ }
+ );
+ procBuilder.setStdout(
+ function(data) {
+ outputData += data;
+ }
+ );
+ procBuilder.setStderr(
+ function(data) {
+ errOutput += data;
+ }
+ );
+ procBuilder.setDone(
+ function(result) {
+ exitCodeObj.value = result.exitCode;
+ }
+ );
+
+ const proc = procBuilder.build();
+ try {
+ subprocess.call(proc).wait();
+ }
+ catch (ex) {
+ EnigmailLog.ERROR("execution.jsm: EnigmailExecution.execCmd: subprocess.call failed with '" + ex.toString() + "'\n");
+ EnigmailLog.DEBUG(" enigmail> DONE with FAILURE\n");
+ exitCodeObj.value = -1;
+ }
+ EnigmailLog.DEBUG(" enigmail> DONE\n");
+
+ if (proc.resultData) outputData = proc.resultData;
+ if (proc.errorData) errOutput = proc.errorData;
+
+ EnigmailLog.DEBUG("execution.jsm: EnigmailExecution.execCmd: exitCode = " + exitCodeObj.value + "\n");
+ EnigmailLog.DEBUG("execution.jsm: EnigmailExecution.execCmd: errOutput = " + errOutput.substr(0, 500) + "\n");
+
+
+ if (!retStatusObj) {
+ retStatusObj = {};
+ }
+
+ errorMsgObj.value = EnigmailErrorHandling.parseErrorOutput(errOutput, retStatusObj);
+
+ statusFlagsObj.value = retStatusObj.statusFlags;
+ statusMsgObj.value = retStatusObj.statusMsg;
+ const blockSeparation = retStatusObj.blockSeparation;
+
+ exitCodeObj.value = EnigmailExecution.fixExitCode(exitCodeObj.value, statusFlagsObj);
+
+ if (blockSeparation.indexOf(" ") > 0) {
+ exitCodeObj.value = 2;
+ }
+
+ EnigmailLog.CONSOLE(errorMsgObj.value + "\n");
+
+ return outputData;
+ },
+
+ /**
+ * Execute a command and asynchronously, and return a Promise
+ * Accepts input and returns error message and statusFlags.
+ *
+ * @param {String/nsIFile} command: either full path to executable
+ * or: object referencing executable
+ * @param {Array of Strings} args: command line parameters for executable
+ * @param {String} input: data to pass to subprocess via stdin
+ * @param {Object} subprocessHandle: handle to subprocess. The subprocess may be
+ * killed via subprocessHandle.value.killProcess();
+ *
+ * @return {Promise<Object>}: Object with:
+ * - {Number} exitCode
+ * - {String} stdoutData - unmodified data from stdout
+ * - {String} stderrData - unmodified data from stderr
+ * - {String} errorMsg - error message from parseErrorOutput()
+ * - {Number} statusFlags
+ * - {String} statusMsg - pre-processed status messages (without [GNUPG:])
+ * - blockSeparation
+ * - isKilled: 0
+ */
+
+ execAsync: function(command, args, input, subprocessHandle = null) {
+ EnigmailLog.WRITE("execution.jsm: execAsync: command = '" + command.path + "'\n");
+ return new Promise((resolve, reject) => {
+
+ if ((typeof input) != "string") input = "";
+
+ let outputData = "";
+ let errOutput = "";
+ let returnObj = {
+ exitCode: -1,
+ stdoutData: "",
+ stderrData: "",
+ errorMsg: "",
+ statusFlags: 0,
+ statusMsg: "",
+ blockSeparation: "",
+ isKilled: 0
+ };
+
+ EnigmailLog.CONSOLE("enigmail> " + EnigmailFiles.formatCmdLine(command, args) + "\n");
+ EnigmailLog.CONSOLE(input + "\n");
+
+ const procBuilder = new EnigmailExecution.processBuilder();
+ procBuilder.setCommand(command);
+ procBuilder.setArguments(args);
+ procBuilder.setEnvironment(EnigmailCore.getEnvList());
+ procBuilder.setStdin(
+ function(pipe) {
+ if (input.length > 0) {
+ pipe.write(input);
+ }
+ pipe.close();
+ }
+ );
+ procBuilder.setStdout(
+ function(data) {
+ outputData += data;
+ }
+ );
+ procBuilder.setStderr(
+ function(data) {
+ errOutput += data;
+ }
+ );
+
+ procBuilder.setDone(
+ function(result) {
+ let exitCode = result.exitCode;
+ EnigmailLog.DEBUG(" enigmail> DONE\n");
+ EnigmailLog.DEBUG("execution.jsm: execAsync: exitCode = " + exitCode + "\n");
+ EnigmailLog.DEBUG("execution.jsm: execAsync: errOutput = " + errOutput.substr(0, 500) + "\n");
+
+ let retStatusObj = {};
+ let errorMsg = EnigmailErrorHandling.parseErrorOutput(errOutput, retStatusObj);
+
+ let statusFlagsObj = {
+ value: retStatusObj.statusFlags
+ };
+
+ exitCode = EnigmailExecution.fixExitCode(exitCode, statusFlagsObj);
+
+ if (retStatusObj.blockSeparation.indexOf(" ") > 0) {
+ exitCode = 2;
+ }
+
+ EnigmailLog.CONSOLE(errorMsg + "\n");
+ returnObj.exitCode = exitCode;
+ returnObj.stdoutData = outputData;
+ returnObj.stderrData = errOutput;
+ returnObj.errorMsg = errorMsg;
+ returnObj.statusFlags = statusFlagsObj.value;
+ returnObj.statusMsg = retStatusObj.statusMsg;
+ returnObj.blockSeparation = retStatusObj.blockSeparation;
+
+ resolve(returnObj);
+ }
+ );
+ const proc = procBuilder.build();
+ try {
+ let p = subprocess.call(proc);
+ if (subprocessHandle) {
+ p.killProcess = function(hardKill) {
+ returnObj.isKilled = 1;
+ this.kill(hardKill);
+ };
+ subprocessHandle.value = p;
+ }
+ }
+ catch (ex) {
+ EnigmailLog.ERROR("execution.jsm: execAsync: subprocess.call failed with '" + ex.toString() + "'\n");
+ EnigmailLog.DEBUG(" enigmail> DONE with FAILURE\n");
+ reject(returnObj);
+ }
+ });
+ },
+
+ /**
+ * Fix the exit code of GnuPG (which may be wrong in some circumstances)
+ *
+ * @exitCode: Number - the exitCode obtained from GnuPG
+ * @statusFlagsObj: Object - the statusFlagsObj as received from parseErrorOutput()
+ *
+ * @return: Number - fixed exit code
+ */
+ fixExitCode: function(exitCode, statusFlagsObj) {
+ EnigmailLog.DEBUG("execution.jsm: EnigmailExecution.fixExitCode: agentType: " + EnigmailExecution.agentType + " exitCode: " + exitCode + " statusFlags " + statusFlagsObj.statusFlags + "\n");
+
+ const statusFlags = statusFlagsObj.statusFlags;
+
+ if (exitCode !== 0) {
+ if ((statusFlags & (EnigmailConstants.BAD_PASSPHRASE | EnigmailConstants.UNVERIFIED_SIGNATURE)) &&
+ (statusFlags & EnigmailConstants.DECRYPTION_OKAY)) {
+ EnigmailLog.DEBUG("enigmailCommon.jsm: Enigmail.fixExitCode: Changing exitCode for decrypted msg " + exitCode + "->0\n");
+ exitCode = 0;
+ }
+ if ((EnigmailExecution.agentType === "gpg") && (exitCode == 256) && (loadOS().getOS() == "WINNT")) {
+ EnigmailLog.WARNING("enigmailCommon.jsm: Enigmail.fixExitCode: Using gpg and exit code is 256. You seem to use cygwin-gpg, activating countermeasures.\n");
+ if (statusFlags & (EnigmailConstants.BAD_PASSPHRASE | EnigmailConstants.UNVERIFIED_SIGNATURE)) {
+ EnigmailLog.WARNING("enigmailCommon.jsm: Enigmail.fixExitCode: Changing exitCode 256->2\n");
+ exitCode = 2;
+ }
+ else {
+ EnigmailLog.WARNING("enigmailCommon.jsm: Enigmail.fixExitCode: Changing exitCode 256->0\n");
+ exitCode = 0;
+ }
+ }
+ }
+ else {
+ if (statusFlags & (EnigmailConstants.INVALID_RECIPIENT | EnigmailConstants.DECRYPTION_FAILED | EnigmailConstants.BAD_ARMOR |
+ EnigmailConstants.MISSING_PASSPHRASE | EnigmailConstants.BAD_PASSPHRASE)) {
+ exitCode = 1;
+ }
+ else if (typeof(statusFlagsObj.extendedStatus) === "string" && statusFlagsObj.extendedStatus.search(/\bdisp:/) >= 0) {
+ exitCode = 1;
+ }
+ }
+
+ return exitCode;
+ },
+
+ processBuilder: function() {
+ this.process = {};
+ this.setCommand = function(command) {
+ this.process.command = command;
+ };
+ this.setArguments = function(args) {
+ this.process.arguments = args;
+ };
+ this.setEnvironment = function(envList) {
+ this.process.environment = envList;
+ };
+ this.setStdin = function(stdin) {
+ this.process.stdin = stdin;
+ };
+ this.setStdout = function(stdout) {
+ this.process.stdout = stdout;
+ };
+ this.setStderr = function(stderr) {
+ this.process.stderr = stderr;
+ };
+ this.setDone = function(done) {
+ this.process.done = done;
+ };
+ this.build = function() {
+ this.process.charset = null;
+ this.process.mergeStderr = false;
+ this.process.resultData = "";
+ this.process.errorData = "";
+ this.process.exitCode = -1;
+ return this.process;
+ };
+ return this;
+ },
+
+ execCmd2: function(command, args, stdinFunc, stdoutFunc, doneFunc) {
+ EnigmailLog.CONSOLE("enigmail> " + EnigmailFiles.formatCmdLine(command, args) + "\n");
+ const procBuilder = new EnigmailExecution.processBuilder();
+ procBuilder.setCommand(command);
+ procBuilder.setArguments(args);
+ procBuilder.setEnvironment(EnigmailCore.getEnvList());
+ procBuilder.setStdin(stdinFunc);
+ procBuilder.setStdout(stdoutFunc);
+ procBuilder.setDone(doneFunc);
+ const proc = procBuilder.build();
+ subprocess.call(proc).wait();
+ },
+
+
+ /**
+ * simple listener for using with execStart
+ *
+ * stdinFunc: optional function to write to stdin
+ * doneFunc : optional function that is called when the process is terminated
+ */
+ newSimpleListener: function(stdinFunc, doneFunc) {
+ const simpleListener = {
+ stdoutData: "",
+ stderrData: "",
+ exitCode: -1,
+ stdin: function(pipe) {
+ if (stdinFunc) {
+ stdinFunc(pipe);
+ }
+ else {
+ pipe.close();
+ }
+ },
+ stdout: function(data) {
+ simpleListener.stdoutData += data;
+ },
+ stderr: function(data) {
+ simpleListener.stderrData += data;
+ },
+ done: function(exitCode) {
+ simpleListener.exitCode = exitCode;
+ if (doneFunc) {
+ doneFunc(exitCode);
+ }
+ }
+ };
+
+ return simpleListener;
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/files.jsm
@@ -0,0 +1,532 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+const EXPORTED_SYMBOLS = ["EnigmailFiles"];
+
+const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
+const EnigmailOS = ChromeUtils.import("chrome://openpgp/content/modules/os.jsm").EnigmailOS;
+const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
+const EnigmailLazy = ChromeUtils.import("chrome://openpgp/content/modules/lazy.jsm").EnigmailLazy;
+Components.utils.importGlobalProperties(["TextDecoder"]);
+
+const {
+ OS
+} = ChromeUtils.import("resource://gre/modules/osfile.jsm", {});
+
+const lazyStream = EnigmailLazy.loader("enigmail/streams.jsm", "EnigmailStreams");
+const lazyLog = EnigmailLazy.loader("enigmail/log.jsm", "EnigmailLog");
+
+const NS_FILE_CONTRACTID = "@mozilla.org/file/local;1";
+const NS_LOCALFILEOUTPUTSTREAM_CONTRACTID = "@mozilla.org/network/file-output-stream;1";
+const NS_IOSERVICE_CONTRACTID = "@mozilla.org/network/io-service;1";
+const NS_SCRIPTABLEINPUTSTREAM_CONTRACTID = "@mozilla.org/scriptableinputstream;1";
+const DIRSERVICE_CONTRACTID = "@mozilla.org/file/directory_service;1";
+
+const NS_RDONLY = 0x01;
+const NS_WRONLY = 0x02;
+const NS_CREATE_FILE = 0x08;
+const NS_TRUNCATE = 0x20;
+const DEFAULT_FILE_PERMS = 0o600;
+
+function potentialWindowsExecutable(file) {
+ if (EnigmailOS.isDosLike) {
+ return file + ".exe";
+ }
+ return file;
+}
+
+var EnigmailFiles = {
+ /**
+ * potentialWindowsExecutable appends .exe to a file
+ *
+ * @param String file - file path or executable name to append .exe to
+ *
+ * @return String file - modified file path or executable name
+ */
+ potentialWindowsExecutable: potentialWindowsExecutable,
+
+ isAbsolutePath: function(filePath, isDosLike) {
+ // Check if absolute path
+ if (isDosLike) {
+ return ((filePath.search(/^\w+:\\/) === 0) || (filePath.search(/^\\\\/) === 0) ||
+ (filePath.search(/^\/\//) === 0));
+ }
+ else {
+ return (filePath.search(/^\//) === 0);
+ }
+ },
+
+ /**
+ * resolvePathWithEnv tries to resolve an file's path with the environment PATH variable.
+ *
+ * @param String file - file to be resolved
+ *
+ * @return String foundPath - Returns found path. If no path is found, returns null.
+ */
+ resolvePathWithEnv: function(executable) {
+ let envSvc = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
+ const foundPath = EnigmailFiles.resolvePath(potentialWindowsExecutable(executable), envSvc.get("PATH"), EnigmailOS.isDosLike);
+ if (foundPath !== null) {
+ foundPath.normalize();
+ }
+ return foundPath;
+ },
+
+ resolvePath: function(filePath, envPath, isDosLike) {
+ lazyLog().DEBUG("files.jsm: resolvePath: filePath=" + filePath + "\n");
+
+ if (EnigmailFiles.isAbsolutePath(filePath, isDosLike))
+ return filePath;
+
+ if (!envPath)
+ return null;
+
+ const fileNames = filePath.split(";");
+
+ const pathDirs = envPath.split(isDosLike ? ";" : ":");
+
+ for (let i = 0; i < fileNames.length; i++) {
+ for (let j = 0; j < pathDirs.length; j++) {
+ try {
+ const pathDir = Cc[NS_FILE_CONTRACTID].createInstance(Ci.nsIFile);
+
+ lazyLog().DEBUG("files.jsm: resolvePath: checking for " + pathDirs[j] + "/" + fileNames[i] + "\n");
+
+ EnigmailFiles.initPath(pathDir, pathDirs[j]);
+
+ try {
+ if (pathDir.exists() && pathDir.isDirectory()) {
+ pathDir.appendRelativePath(fileNames[i]);
+
+ if (pathDir.exists() && !pathDir.isDirectory()) {
+ return pathDir;
+ }
+ }
+ }
+ catch (ex) {}
+ }
+ catch (ex) {}
+ }
+ }
+ return null;
+ },
+
+ createFileStream: function(filePath, permissions) {
+ try {
+ let localFile;
+ if (typeof filePath == "string") {
+ localFile = Cc[NS_FILE_CONTRACTID].createInstance(Ci.nsIFile);
+ EnigmailFiles.initPath(localFile, filePath);
+ }
+ else {
+ localFile = filePath.QueryInterface(Ci.nsIFile);
+ }
+
+ if (localFile.exists()) {
+
+ if (localFile.isDirectory() || !localFile.isWritable())
+ throw Components.results.NS_ERROR_FAILURE;
+
+ if (!permissions)
+ permissions = localFile.permissions;
+ }
+
+ if (!permissions)
+ permissions = DEFAULT_FILE_PERMS;
+
+ const flags = NS_WRONLY | NS_CREATE_FILE | NS_TRUNCATE;
+
+ const fileStream = Cc[NS_LOCALFILEOUTPUTSTREAM_CONTRACTID].createInstance(Ci.nsIFileOutputStream);
+
+ fileStream.init(localFile, flags, permissions, 0);
+
+ return fileStream;
+
+ }
+ catch (ex) {
+ lazyLog().ERROR("files.jsm: createFileStream: Failed to create " + filePath + "\n");
+ return null;
+ }
+ },
+
+ // path initialization function
+ // uses persistentDescriptor in case that initWithPath fails
+ // (seems to happen frequently with UTF-8 characters in path names)
+ initPath: function(localFileObj, pathStr) {
+ localFileObj.initWithPath(pathStr);
+
+ if (!localFileObj.exists()) {
+ localFileObj.persistentDescriptor = pathStr;
+ }
+ },
+
+ /**
+ * Read the contents of a text file into a string
+ *
+ * @param fileObj: Object (nsIFile)
+ *
+ * @return String (file contents)
+ */
+ readFile: function(fileObj) {
+ let fileContents = "";
+
+ if (fileObj.exists()) {
+ let inspector = Cc["@mozilla.org/jsinspector;1"].createInstance(Ci.nsIJSInspector);
+
+ let decoder = new TextDecoder();
+ OS.File.read(fileObj.path).then(arr => {
+ fileContents = EnigmailData.arrayBufferToString(arr); // Convert the array to a text
+ inspector.exitNestedEventLoop();
+ }).catch(err => {
+ inspector.exitNestedEventLoop();
+ });
+
+ inspector.enterNestedEventLoop(0); // wait for async process to terminate
+ }
+
+ return fileContents;
+ },
+
+ /** Read the contents of a file with binary data into a string
+ * @param fileObj: Object (nsIFile)
+ *
+ * @return String (file contents)
+ */
+ readBinaryFile: function(fileObj) {
+ let fileContents = "";
+
+ if (fileObj.exists()) {
+ let inspector = Cc["@mozilla.org/jsinspector;1"].createInstance(Ci.nsIJSInspector);
+
+ OS.File.read(fileObj.path).then(arr => {
+ for (let i = 0; i < arr.length; i++) {
+ fileContents += String.fromCharCode(arr[i]);
+ }
+
+ inspector.exitNestedEventLoop();
+ }).catch(err => {
+ inspector.exitNestedEventLoop();
+ });
+
+ inspector.enterNestedEventLoop(0); // wait for async process to terminate
+ }
+
+ return fileContents;
+ },
+
+ formatCmdLine: function(command, args) {
+ function getQuoted(str) {
+ str = str.toString();
+
+ let i = str.indexOf(" ");
+ if (i >= 0) {
+ return '"' + str + '"';
+ }
+ else {
+ return str;
+ }
+ }
+
+ if (command instanceof Ci.nsIFile) {
+ command = EnigmailFiles.getFilePathDesc(command);
+ }
+
+ const cmdStr = getQuoted(command) + " ";
+ const argStr = args.map(getQuoted).join(" ").replace(/\\\\/g, '\\');
+ return cmdStr + argStr;
+ },
+
+ getFilePathDesc: function(nsFileObj) {
+ if (EnigmailOS.getOS() == "WINNT") {
+ return nsFileObj.persistentDescriptor;
+ }
+ else {
+ return nsFileObj.path;
+ }
+ },
+
+ getFilePath: function(nsFileObj) {
+ return EnigmailData.convertToUnicode(EnigmailFiles.getFilePathDesc(nsFileObj), "utf-8");
+ },
+
+ getEscapedFilename: function(fileNameStr) {
+ if (EnigmailOS.isDosLike) {
+ // escape the backslashes and the " character (for Windows and OS/2)
+ fileNameStr = fileNameStr.replace(/([\\"])/g, "\\$1");
+ }
+
+ if (EnigmailOS.getOS() == "WINNT") {
+ // replace leading "\\" with "//"
+ fileNameStr = fileNameStr.replace(/^\\\\*/, "//");
+ }
+ return fileNameStr;
+ },
+
+ /**
+ * get the temporary folder
+ *
+ * @return nsIFile object holding a reference to the temp directory
+ */
+ getTempDirObj: function() {
+ const TEMPDIR_PROP = "TmpD";
+
+ try {
+ const dsprops = Cc[DIRSERVICE_CONTRACTID].getService().
+ QueryInterface(Ci.nsIProperties);
+ return dsprops.get(TEMPDIR_PROP, Ci.nsIFile);
+ }
+ catch (ex) {
+ // let's guess ...
+ const tmpDirObj = Cc[NS_FILE_CONTRACTID].createInstance(Ci.nsIFile);
+ if (EnigmailOS.getOS() == "WINNT") {
+ tmpDirObj.initWithPath("C:/TEMP");
+ }
+ else {
+ tmpDirObj.initWithPath("/tmp");
+ }
+ return tmpDirObj;
+ }
+ },
+
+ /**
+ * get the temporary folder as string
+ *
+ * @return String containing the temp directory name
+ */
+ getTempDir: function() {
+ return EnigmailFiles.getTempDirObj().path;
+ },
+
+ /**
+ * create a new folder as subfolder of the temporary directory
+ *
+ * @param dirName String - name of subfolder
+ * @param unique Boolean - if true, the directory is guaranteed to be unique
+ *
+ * @return nsIFile object holding a reference to the created directory
+ */
+ createTempSubDir: function(dirName, unique = false) {
+ const localFile = EnigmailFiles.getTempDirObj().clone();
+
+ localFile.append(dirName);
+ if (unique) {
+ localFile.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 509 /* = 0775 */ );
+ }
+ else {
+ localFile.create(Ci.nsIFile.DIRECTORY_TYPE, 509 /* = 0775 */ );
+ }
+
+ return localFile;
+ },
+
+ /**
+ * Ensure that a directory exists and is writeable.
+ *
+ * @param dirObj Object - nsIFile object for the directory to test
+ * @param permissions Number - file permissions in Unix style (e.g. 0700)
+ *
+ * @return Number:
+ * 0 - OK: directory exists (or was created) and is writeable
+ * 1 - NOK: Directory does not exist (and cannot be created)
+ * 2 - NOK: Directory exists but is readonly (and cannot be modified)
+ * 3 - NOK: File object with required name exists but is not a directory
+ */
+ ensureWritableDirectory: function(dirObj, permissions) {
+ let retVal = -1;
+ try {
+ if (dirObj.isDirectory()) {
+ try {
+ if (dirObj.isWritable()) {
+ retVal = 0;
+ }
+ else {
+ dirObj.permissions = permissions;
+ retVal = 0;
+ }
+ }
+ catch (x) {
+ retVal = 2;
+ }
+ }
+ else {
+ retVal = 3;
+ }
+ }
+ catch (x) {
+ // directory doesn't exist
+ try {
+ dirObj.create(Ci.nsIFile.DIRECTORY_TYPE, permissions);
+ retVal = 0;
+ }
+ catch (x2) {
+ retVal = 1;
+ }
+ }
+ return retVal;
+ },
+
+
+ /**
+ * Write data to a file
+ * @filePath |string| or |nsIFile| object - the file to be created
+ * @data |string| - the data to write to the file
+ * @permissions |number| - file permissions according to Unix spec (0600 by default)
+ *
+ * @return true if data was written successfully, false otherwise
+ */
+ writeFileContents: function(filePath, data, permissions) {
+ try {
+ const fileOutStream = EnigmailFiles.createFileStream(filePath, permissions);
+
+ if (data.length) {
+ if (fileOutStream.write(data, data.length) != data.length) {
+ throw Components.results.NS_ERROR_FAILURE;
+ }
+
+ fileOutStream.flush();
+ }
+ fileOutStream.close();
+ }
+ catch (ex) {
+ lazyLog().ERROR("files.jsm: writeFileContents: Failed to write to " + filePath + "\n");
+ return false;
+ }
+
+ return true;
+ },
+
+ /**
+ * Create a text file from the contents of a given URL
+ *
+ * @param srcUrl: String - the URL to download
+ * @param outFile: nsIFile object - the file to create
+ *
+ * no return value
+ */
+ writeUrlToFile: function(srcUrl, outFile) {
+ lazyLog().DEBUG("files.jsm: writeUrlToFile(" + outFile.path + ")\n");
+
+ var ioServ = Cc[NS_IOSERVICE_CONTRACTID].getService(Ci.nsIIOService);
+ var msgUri = ioServ.newURI(srcUrl, null, null);
+ var channel = lazyStream().createChannel(msgUri);
+ var istream = channel.open();
+
+ var fstream = Cc["@mozilla.org/network/safe-file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
+ var buffer = Cc["@mozilla.org/network/buffered-output-stream;1"].createInstance(Ci.nsIBufferedOutputStream);
+ fstream.init(outFile, 0x04 | 0x08 | 0x20, 0x180, 0); // write, create, truncate
+ buffer.init(fstream, 8192);
+
+ while (istream.available() > 0) {
+ buffer.writeFrom(istream, istream.available());
+ }
+
+ // Close the output streams
+ if (buffer instanceof Ci.nsISafeOutputStream)
+ buffer.finish();
+ else
+ buffer.close();
+
+ if (fstream instanceof Ci.nsISafeOutputStream)
+ fstream.finish();
+ else
+ fstream.close();
+
+ // Close the input stream
+ istream.close();
+ },
+
+ // return the useable path (for gpg) of a file object
+ getFilePathReadonly: function(nsFileObj, creationMode) {
+ if (creationMode === null) creationMode = NS_RDONLY;
+ return nsFileObj.path;
+ },
+
+ /**
+ * Create an empty ZIP file
+ *
+ * @param nsFileObj - nsIFile object: reference to the file to be created
+ *
+ * @return nsIZipWriter object allow to perform write operations on the ZIP file
+ */
+ createZipFile: function(nsFileObj) {
+ const zipW = Cc['@mozilla.org/zipwriter;1'].createInstance(Ci.nsIZipWriter);
+ zipW.open(nsFileObj, NS_WRONLY | NS_CREATE_FILE | NS_TRUNCATE);
+
+ return zipW;
+ },
+
+ /**
+ * Open a ZIP file for reading
+ *
+ * @param nsFileObj - nsIFile object: reference to the file to be created
+ *
+ * @return nsIZipReader object allow to perform read operations on the ZIP file
+ */
+ openZipFile: function(nsFileObj) {
+ const zipR = Cc['@mozilla.org/libjar/zip-reader;1'].createInstance(Ci.nsIZipReader);
+ zipR.open(nsFileObj);
+
+ return zipR;
+ },
+
+
+ /**
+ * Unpack a ZIP file to a directory
+ *
+ * @param zipFile - nsIZipReader object: file to be extracted
+ * @param targetDir - nsIFile object: target directory
+ *
+ * @return Boolean: true if extraction successfull, false otherwise
+ */
+ extractZipFile: function(zipFile, targetDir) {
+
+ // create missing parent directories
+ function createDirWithParents(dirObj) {
+ if (!dirObj.parent.exists()) {
+ createDirWithParents(dirObj.parent);
+ }
+ dirObj.create(dirObj.DIRECTORY_TYPE, 493);
+ }
+
+ try {
+ let zipReader = EnigmailFiles.openZipFile(zipFile);
+ let f = zipReader.findEntries("*");
+
+ while (f.hasMore()) {
+ let t = targetDir.clone();
+ let i = f.getNext();
+ let entry = zipReader.getEntry(i);
+
+ if (!EnigmailOS.isDosLike) {
+ t.initWithPath(t.path + "/" + i);
+ }
+ else {
+ i = i.replace(/\//g, "\\");
+ t.initWithPath(t.path + "\\" + i);
+ }
+
+ if (!t.parent.exists()) {
+ createDirWithParents(t.parent);
+ }
+
+ if (!(entry.isDirectory || i.search(/[\/\\]$/) >= 0)) {
+ zipReader.extract(i, t);
+ }
+ }
+
+ zipReader.close();
+
+ return true;
+ }
+ catch (ex) {
+ lazyLog().ERROR("files.jsm: extractZipFile: Failed to create ZIP: " + ex + "\n");
+ return false;
+ }
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/filters.jsm
@@ -0,0 +1,549 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailFilters"];
+
+const EnigmailLazy = ChromeUtils.import("chrome://openpgp/content/modules/lazy.jsm").EnigmailLazy;
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
+const EnigmailPersistentCrypto = ChromeUtils.import("chrome://openpgp/content/modules/persistentCrypto.jsm").EnigmailPersistentCrypto;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailFuncs = ChromeUtils.import("chrome://openpgp/content/modules/funcs.jsm").EnigmailFuncs;
+const EnigmailKeyRing = ChromeUtils.import("chrome://openpgp/content/modules/keyRing.jsm").EnigmailKeyRing;
+const EnigmailStreams = ChromeUtils.import("chrome://openpgp/content/modules/streams.jsm").EnigmailStreams;
+const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
+const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
+const jsmime = ChromeUtils.import("resource:///modules/jsmime.jsm").jsmime;
+const NetUtil = ChromeUtils.import("resource://gre/modules/NetUtil.jsm").NetUtil;
+const EnigmailMime = ChromeUtils.import("chrome://openpgp/content/modules/mime.jsm").EnigmailMime;
+const EnigmailCompat = ChromeUtils.import("chrome://openpgp/content/modules/compat.jsm").EnigmailCompat;
+
+const getDialog = EnigmailLazy.loader("enigmail/dialog.jsm", "EnigmailDialog");
+
+var gNewMailListenerInitiated = false;
+
+/**
+ * filter action for creating a decrypted version of the mail and
+ * deleting the original mail at the same time
+ */
+
+const filterActionMoveDecrypt = {
+ apply: function(aMsgHdrs, aActionValue, aListener, aType, aMsgWindow) {
+
+ EnigmailLog.DEBUG("filters.jsm: filterActionMoveDecrypt: Move to: " + aActionValue + "\n");
+
+ var msgHdrs = [];
+
+ for (var i = 0; i < aMsgHdrs.length; i++) {
+ msgHdrs.push(aMsgHdrs.queryElementAt(i, Ci.nsIMsgDBHdr));
+ }
+
+ EnigmailPersistentCrypto.dispatchMessages(msgHdrs, aActionValue, aListener, true);
+ },
+
+ isValidForType: function(type, scope) {
+ return true;
+ },
+
+ validateActionValue: function(value, folder, type) {
+ getDialog().alert(null, EnigmailLocale.getString("filter.decryptMove.warnExperimental"));
+
+ if (value === "") {
+ return EnigmailLocale.getString("filter.folderRequired");
+ }
+
+ return null;
+ }
+};
+
+/**
+ * filter action for creating a decrypted copy of the mail, leaving the original
+ * message untouched
+ */
+const filterActionCopyDecrypt = {
+ apply: function(aMsgHdrs, aActionValue, aListener, aType, aMsgWindow) {
+ EnigmailLog.DEBUG("filters.jsm: filterActionCopyDecrypt: Copy to: " + aActionValue + "\n");
+
+ var msgHdrs = [];
+
+ for (var i = 0; i < aMsgHdrs.length; i++) {
+ msgHdrs.push(aMsgHdrs.queryElementAt(i, Ci.nsIMsgDBHdr));
+ }
+
+ EnigmailPersistentCrypto.dispatchMessages(msgHdrs, aActionValue, aListener, false);
+ },
+
+ isValidForType: function(type, scope) {
+ EnigmailLog.DEBUG("filters.jsm: filterActionCopyDecrypt.isValidForType(" + type + ")\n");
+
+ let r = true;
+ return r;
+ },
+
+ validateActionValue: function(value, folder, type) {
+ EnigmailLog.DEBUG("filters.jsm: filterActionCopyDecrypt.validateActionValue(" + value + ")\n");
+
+ if (value === "") {
+ return EnigmailLocale.getString("filter.folderRequired");
+ }
+
+ return null;
+ }
+};
+
+/**
+ * filter action for to encrypt a mail to a specific key
+ */
+const filterActionEncrypt = {
+ apply: function(aMsgHdrs, aActionValue, aListener, aType, aMsgWindow) {
+ // Ensure KeyRing is loaded.
+ if (aMsgWindow) {
+ EnigmailCore.getService(aMsgWindow.domWindow);
+ }
+ else {
+ EnigmailCore.getService();
+ }
+ EnigmailKeyRing.getAllKeys();
+
+ EnigmailLog.DEBUG("filters.jsm: filterActionEncrypt: Encrypt to: " + aActionValue + "\n");
+ let keyObj = EnigmailKeyRing.getKeyById(aActionValue);
+
+ if (keyObj === null) {
+ EnigmailLog.DEBUG("filters.jsm: failed to find key by id: " + aActionValue + "\n");
+ let keyId = EnigmailKeyRing.getValidKeyForRecipient(aActionValue);
+ if (keyId) {
+ keyObj = EnigmailKeyRing.getKeyById(keyId);
+ }
+ }
+
+ if (keyObj === null && aListener) {
+ EnigmailLog.DEBUG("filters.jsm: no valid key - aborting\n");
+
+ aListener.OnStartCopy();
+ aListener.OnStopCopy(1);
+
+ return;
+ }
+
+ EnigmailLog.DEBUG("filters.jsm: key to encrypt to: " + JSON.stringify(keyObj) + ", userId: " + keyObj.userId + "\n");
+
+ var msgHdrs = [];
+ for (let i = 0; i < aMsgHdrs.length; i++) {
+ let msg = aMsgHdrs.queryElementAt(i, Ci.nsIMsgDBHdr);
+ // Maybe skip messages here if they are already encrypted to
+ // the target key? There might be some use case for unconditionally
+ // encrypting here. E.g. to use the local preferences and remove all
+ // other recipients.
+ // Also not encrypting to already encrypted messages would make the
+ // behavior less transparent as it's not obvious.
+ msgHdrs.push(msg);
+ }
+
+ if (msgHdrs.length) {
+ EnigmailPersistentCrypto.dispatchMessages(msgHdrs, null /* same folder */ , aListener,
+ true /* move */ , keyObj /* target key */ );
+ }
+ },
+
+ isValidForType: function(type, scope) {
+ return true;
+ },
+
+ validateActionValue: function(value, folder, type) {
+ // Initialize KeyRing. Ugly as it blocks the GUI but
+ // we need it.
+ EnigmailCore.getService();
+ EnigmailKeyRing.getAllKeys();
+
+ EnigmailLog.DEBUG("filters.jsm: validateActionValue: Encrypt to: " + value + "\n");
+ if (value === "") {
+ return EnigmailLocale.getString("filter.keyRequired");
+ }
+
+ let keyObj = EnigmailKeyRing.getKeyById(value);
+
+ if (keyObj === null) {
+ EnigmailLog.DEBUG("filters.jsm: failed to find key by id. Looking for uid.\n");
+ let keyId = EnigmailKeyRing.getValidKeyForRecipient(value);
+ if (keyId) {
+ keyObj = EnigmailKeyRing.getKeyById(keyId);
+ }
+ }
+
+ if (keyObj === null) {
+ return EnigmailLocale.getString("filter.keyNotFound", [value]);
+ }
+
+ if (!keyObj.secretAvailable) {
+ // We warn but we allow it. There might be use cases where
+ // thunderbird + enigmail is used as a gateway filter with
+ // the secret not available on one machine and the decryption
+ // is intended to happen on different systems.
+ getDialog().alert(null, EnigmailLocale.getString("filter.warn.keyNotSecret", [value]));
+ }
+
+ return null;
+ }
+};
+
+function isPGPEncrypted(data) {
+ // We only check the first mime subpart for application/pgp-encrypted.
+ // If it is text/plain or text/html we look into that for the
+ // message marker.
+ // If there are no subparts we just look in the body.
+ //
+ // This intentionally does not match more complex cases
+ // with sub parts beeing encrypted etc. as auto processing
+ // these kinds of mails will be error prone and better not
+ // done through a filter
+
+ var mimeTree = EnigmailMime.getMimeTree(data, true);
+ if (!(mimeTree.subParts.length)) {
+ // No subParts. Check for PGP Marker in Body
+ return mimeTree.body.indexOf('-----BEGIN PGP MESSAGE-----') >= 0;
+ }
+
+ // Check the type of the first subpart.
+ var firstPart = mimeTree.subParts[0];
+ var ct = firstPart.fullContentType;
+ if (typeof(ct) == "string") {
+ ct = ct.replace(/[\r\n]/g, " ");
+ // Proper PGP/MIME ?
+ if (ct.search(/application\/pgp-encrypted/i) >= 0) {
+ return true;
+ }
+ // Look into text/plain pgp messages and text/html messages.
+ if (ct.search(/text\/plain/i) >= 0 ||
+ ct.search(/text\/html/i) >= 0) {
+ return firstPart.body.indexOf('-----BEGIN PGP MESSAGE-----') >= 0;
+ }
+ }
+ return false;
+}
+
+/**
+ * filter term for OpenPGP Encrypted mail
+ */
+const filterTermPGPEncrypted = {
+ id: EnigmailConstants.FILTER_TERM_PGP_ENCRYPTED,
+ name: EnigmailLocale.getString("filter.term.pgpencrypted.label"),
+ needsBody: true,
+ match: function(aMsgHdr, searchValue, searchOp) {
+ var folder = aMsgHdr.folder;
+ var stream = folder.getMsgInputStream(aMsgHdr, {});
+
+ var messageSize = folder.hasMsgOffline(aMsgHdr.messageKey) ? aMsgHdr.offlineMessageSize : aMsgHdr.messageSize;
+ var scriptInput = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance();
+ var data;
+ try {
+ data = NetUtil.readInputStreamToString(stream, messageSize);
+ }
+ catch (ex) {
+ EnigmailLog.DEBUG("filters.jsm: filterTermPGPEncrypted: failed to get data.\n");
+ // If we don't know better to return false.
+ stream.close();
+ return false;
+ }
+
+ var isPGP = isPGPEncrypted(data);
+
+ stream.close();
+
+ return ((searchOp == Ci.nsMsgSearchOp.Is && isPGP) ||
+ (searchOp == Ci.nsMsgSearchOp.Isnt && !isPGP));
+ },
+
+ getEnabled: function(scope, op) {
+ return true;
+ },
+
+ getAvailable: function(scope, op) {
+ return true;
+ },
+
+ getAvailableOperators: function(scope, length) {
+ length.value = 2;
+ return [Ci.nsMsgSearchOp.Is, Ci.nsMsgSearchOp.Isnt];
+ }
+};
+
+/**
+ * Add a custom filter action. If the filter already exists, do nothing
+ * (for example, if addon is disabled and re-enabled)
+ *
+ * @param filterObj - nsIMsgFilterCustomAction
+ */
+function addFilterIfNotExists(filterObj) {
+ let filterService = Cc["@mozilla.org/messenger/services/filters;1"].getService(Ci.nsIMsgFilterService);
+
+ let foundFilter = null;
+ try {
+ foundFilter = filterService.getCustomAction(filterObj.id);
+ }
+ catch (ex) {}
+
+ if (!foundFilter) {
+ EnigmailLog.DEBUG("filters.jsm: addFilterIfNotExists: " + filterObj.id + "\n");
+ filterService.addCustomAction(filterObj);
+ }
+}
+
+function initNewMailListener() {
+ EnigmailLog.DEBUG("filters.jsm: initNewMailListener()\n");
+
+ if (!gNewMailListenerInitiated) {
+ let notificationService = Cc["@mozilla.org/messenger/msgnotificationservice;1"]
+ .getService(Ci.nsIMsgFolderNotificationService);
+ notificationService.addListener(newMailListener, notificationService.msgAdded);
+ }
+ gNewMailListenerInitiated = true;
+}
+
+function shutdownNewMailListener() {
+ EnigmailLog.DEBUG("filters.jsm: shutdownNewMailListener()\n");
+
+ if (gNewMailListenerInitiated) {
+ let notificationService = Cc["@mozilla.org/messenger/msgnotificationservice;1"]
+ .getService(Ci.nsIMsgFolderNotificationService);
+ notificationService.removeListener(newMailListener);
+ gNewMailListenerInitiated = false;
+ }
+}
+
+function getIdentityForSender(senderEmail, msgServer) {
+ let accountManager = Cc["@mozilla.org/messenger/account-manager;1"].getService(Ci.nsIMsgAccountManager);
+
+ let identities = accountManager.getIdentitiesForServer(msgServer);
+
+ for (let i = 0; i < identities.length; i++) {
+ let id = identities.queryElementAt(i, Ci.nsIMsgIdentity);
+ if (id.email.toLowerCase() === senderEmail.toLowerCase()) {
+ return id;
+ }
+ }
+
+ return null;
+}
+
+var consumerList = [];
+
+
+function JsmimeEmitter(requireBody) {
+ this.requireBody = requireBody;
+ this.mimeTree = {
+ partNum: "",
+ headers: null,
+ body: "",
+ parent: null,
+ subParts: []
+ };
+ this.stack = [];
+ this.currPartNum = "";
+}
+
+JsmimeEmitter.prototype = {
+
+ createPartObj: function(partNum, headers, parent) {
+ return {
+ partNum: partNum,
+ headers: headers,
+ body: "",
+ parent: parent,
+ subParts: []
+ };
+ },
+
+ getMimeTree: function() {
+ return this.mimeTree.subParts[0];
+ },
+
+ /** JSMime API **/
+ startMessage: function() {
+ this.currentPart = this.mimeTree;
+ },
+ endMessage: function() {},
+
+ startPart: function(partNum, headers) {
+ EnigmailLog.DEBUG("filters.jsm: JsmimeEmitter.startPart: partNum=" + partNum + "\n");
+ //this.stack.push(partNum);
+ let newPart = this.createPartObj(partNum, headers, this.currentPart);
+
+ if (partNum.indexOf(this.currPartNum) === 0) {
+ // found sub-part
+ this.currentPart.subParts.push(newPart);
+ }
+ else {
+ // found same or higher level
+ this.currentPart.subParts.push(newPart);
+ }
+ this.currPartNum = partNum;
+ this.currentPart = newPart;
+ },
+
+ endPart: function(partNum) {
+ EnigmailLog.DEBUG("filters.jsm: JsmimeEmitter.startPart: partNum=" + partNum + "\n");
+ this.currentPart = this.currentPart.parent;
+ },
+
+ deliverPartData: function(partNum, data) {
+ EnigmailLog.DEBUG("filters.jsm: JsmimeEmitter.deliverPartData: partNum=" + partNum + "\n");
+ if (this.requireBody) {
+ if (typeof(data) === "string") {
+ this.currentPart.body += data;
+ }
+ else {
+ this.currentPart.body += EnigmailData.arrayBufferToString(data);
+ }
+ }
+ }
+};
+
+function processIncomingMail(url, requireBody, aMsgHdr) {
+ EnigmailLog.DEBUG("filters.jsm: processIncomingMail()\n");
+
+ let inputStream = EnigmailStreams.newStringStreamListener(msgData => {
+ let opt = {
+ strformat: "unicode",
+ bodyformat: "decode"
+ };
+
+ try {
+ let e = new JsmimeEmitter(requireBody);
+ let p = new jsmime.MimeParser(e, opt);
+ p.deliverData(msgData);
+
+
+ for (let c of consumerList) {
+ try {
+ c.consumeMessage(e.getMimeTree(), msgData, aMsgHdr);
+ }
+ catch (ex) {
+ EnigmailLog.DEBUG("filters.jsm: processIncomingMail: exception: " + ex.toString() + "\n");
+ }
+ }
+ }
+ catch (ex) {}
+ });
+
+ try {
+ let channel = EnigmailStreams.createChannel(url);
+ channel.asyncOpen(inputStream, null);
+ }
+ catch (e) {
+ EnigmailLog.DEBUG("filters.jsm: processIncomingMail: open stream exception " + e.toString() + "\n");
+ }
+}
+
+function getRequireMessageProcessing(aMsgHdr) {
+ let isInbox = aMsgHdr.folder.getFlag(Ci.nsMsgFolderFlags.CheckNew) || aMsgHdr.folder.getFlag(Ci.nsMsgFolderFlags.Inbox);
+ let requireBody = false;
+ let inboxOnly = true;
+ let selfSentOnly = false;
+ let processReadMail = false;
+
+ for (let c of consumerList) {
+ if (!c.incomingMailOnly) {
+ inboxOnly = false;
+ }
+ if (!c.unreadOnly) {
+ processReadMail = true;
+ }
+ if (!c.headersOnly) {
+ requireBody = true;
+ }
+ if (c.selfSentOnly) {
+ selfSentOnly = true;
+ }
+ }
+
+ if (!processReadMail && aMsgHdr.isRead) return null;
+ if (inboxOnly && !isInbox) return null;
+ if (selfSentOnly) {
+ let sender = EnigmailFuncs.parseEmails(aMsgHdr.author, true);
+ let id = null;
+ if (sender && sender[0]) {
+ id = getIdentityForSender(sender[0].email, aMsgHdr.folder.server);
+ }
+
+ if (!id) return null;
+ }
+
+ EnigmailLog.DEBUG("filters.jsm: getRequireMessageProcessing: author: " + aMsgHdr.author + "\n");
+
+ let u = EnigmailCompat.getUrlFromUriSpec(aMsgHdr.folder.getUriForMsg(aMsgHdr));
+
+ if (! u) {
+ return null;
+ }
+
+ let op = (u.spec.indexOf("?") > 0 ? "&" : "?");
+ let url = u.spec + op + "header=enigmailFilter";
+
+ return {
+ url: url,
+ requireBody: requireBody
+ };
+}
+
+const newMailListener = {
+ msgAdded: function(aMsgHdr) {
+ EnigmailLog.DEBUG("filters.jsm: newMailListener.msgAdded() - got new mail in " + aMsgHdr.folder.prettiestName + "\n");
+
+ if (consumerList.length === 0) return;
+
+ let ret = getRequireMessageProcessing(aMsgHdr);
+ if (ret) {
+ processIncomingMail(ret.url, ret.requireBody, aMsgHdr);
+ }
+ }
+};
+
+/**
+ messageStructure - Object:
+ - partNum: String - MIME part number
+ - headers: Object(nsIStructuredHeaders) - MIME part headers
+ - body: String or typedarray - the body part
+ - parent: Object(messageStructure) - link to the parent part
+ - subParts: Array of Object(messageStructure) - array of the sub-parts
+ */
+
+var EnigmailFilters = {
+ onStartup: function() {
+ let filterService = Cc["@mozilla.org/messenger/services/filters;1"].getService(Ci.nsIMsgFilterService);
+ filterService.addCustomTerm(filterTermPGPEncrypted);
+ initNewMailListener();
+ },
+
+ onShutdown: function() {
+ shutdownNewMailListener();
+ },
+
+ /**
+ * add a new consumer to listen to new mails
+ *
+ * @param consumer - Object
+ * - headersOnly: Boolean - needs full message body? [FUTURE]
+ * - incomingMailOnly: Boolean - only work on folder(s) that obtain new mail
+ * (Inbox and folders that listen to new mail)
+ * - unreadOnly: Boolean - only process unread mails
+ * - selfSentOnly: Boolean - only process mails with sender Email == Account Email
+ * - consumeMessage: function(messageStructure, rawMessageData, nsIMsgHdr)
+ */
+ addNewMailConsumer: function(consumer) {
+ EnigmailLog.DEBUG("filters.jsm: addNewMailConsumer()\n");
+ consumerList.push(consumer);
+ },
+
+ removeNewMailConsumer: function(consumer) {
+
+ },
+
+ moveDecrypt: filterActionMoveDecrypt,
+ copyDecrypt: filterActionCopyDecrypt,
+ encrypt: filterActionEncrypt
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/filtersWrapper.jsm
@@ -0,0 +1,169 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailFiltersWrapper"];
+
+
+
+
+
+var gNewMailListenerInitiated = false;
+var gEnigmailFilters = null;
+
+let {
+ EnigmailConstants
+} = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm");
+let {
+ EnigmailLocale
+} = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm");
+
+/**
+ * filter action for creating a decrypted version of the mail and
+ * deleting the original mail at the same time
+ */
+
+const filterActionMoveDecrypt = {
+ id: EnigmailConstants.FILTER_MOVE_DECRYPT,
+ name: EnigmailLocale.getString("filter.decryptMove.label"),
+ value: "movemessage",
+ apply: function(aMsgHdrs, aActionValue, aListener, aType, aMsgWindow) {
+
+ if (gEnigmailFilters) {
+ gEnigmailFilters.moveDecrypt.apply(aMsgHdrs, aActionValue, aListener, aType, aMsgWindow);
+ }
+ else {
+ aListener.OnStartCopy();
+ aListener.OnStopCopy(0);
+ }
+ },
+
+ isValidForType: function(type, scope) {
+ return gEnigmailFilters ? gEnigmailFilters.moveDecrypt.isValidForType(type, scope) : false;
+ },
+
+ validateActionValue: function(value, folder, type) {
+ if (gEnigmailFilters) {
+ return gEnigmailFilters.moveDecrypt.validateActionValue(value, folder, type);
+ }
+ return null;
+ },
+
+ allowDuplicates: false,
+ isAsync: true,
+ needsBody: true
+};
+
+/**
+ * filter action for creating a decrypted copy of the mail, leaving the original
+ * message untouched
+ */
+const filterActionCopyDecrypt = {
+ id: EnigmailConstants.FILTER_COPY_DECRYPT,
+ name: EnigmailLocale.getString("filter.decryptCopy.label"),
+ value: "copymessage",
+ apply: function(aMsgHdrs, aActionValue, aListener, aType, aMsgWindow) {
+ if (gEnigmailFilters) {
+ gEnigmailFilters.copyDecrypt.apply(aMsgHdrs, aActionValue, aListener, aType, aMsgWindow);
+ }
+ else {
+ aListener.OnStartCopy();
+ aListener.OnStopCopy(0);
+ }
+ },
+
+ isValidForType: function(type, scope) {
+ return gEnigmailFilters ? gEnigmailFilters.copyDecrypt.isValidForType(type, scope) : false;
+ },
+
+ validateActionValue: function(value, folder, type) {
+ if (gEnigmailFilters) {
+ return gEnigmailFilters.copyDecrypt.validateActionValue(value, folder, type);
+ }
+ return null;
+ },
+
+ allowDuplicates: false,
+ isAsync: true,
+ needsBody: true
+};
+
+/**
+ * filter action for to encrypt a mail to a specific key
+ */
+const filterActionEncrypt = {
+ id: EnigmailConstants.FILTER_ENCRYPT,
+ name: EnigmailLocale.getString("filter.encrypt.label"),
+ value: "encryptto",
+ apply: function(aMsgHdrs, aActionValue, aListener, aType, aMsgWindow) {
+ if (gEnigmailFilters) {
+ gEnigmailFilters.encrypt.apply(aMsgHdrs, aActionValue, aListener, aType, aMsgWindow);
+ }
+ else {
+ aListener.OnStartCopy();
+ aListener.OnStopCopy(0);
+ }
+ },
+
+ isValidForType: function(type, scope) {
+ return gEnigmailFilters ? gEnigmailFilters.encrypt.isValidForType() : false;
+ },
+
+ validateActionValue: function(value, folder, type) {
+ if (gEnigmailFilters) {
+ return gEnigmailFilters.encrypt.validateActionValue(value, folder, type);
+ }
+ return null;
+
+ },
+
+ allowDuplicates: false,
+ isAsync: true,
+ needsBody: true
+};
+
+
+/**
+ * Add a custom filter action. If the filter already exists, do nothing
+ * (for example, if addon is disabled and re-enabled)
+ *
+ * @param filterObj - nsIMsgFilterCustomAction
+ */
+function addFilterIfNotExists(filterObj) {
+ let filterService = Cc["@mozilla.org/messenger/services/filters;1"].getService(Ci.nsIMsgFilterService);
+
+ let foundFilter = null;
+ try {
+ foundFilter = filterService.getCustomAction(filterObj.id);
+ }
+ catch (ex) {}
+
+ if (!foundFilter) {
+ filterService.addCustomAction(filterObj);
+ }
+}
+
+var EnigmailFiltersWrapper = {
+ onStartup: function() {
+ let {
+ EnigmailFilters
+ } = ChromeUtils.import("chrome://openpgp/content/modules/filters.jsm");
+ gEnigmailFilters = EnigmailFilters;
+
+ addFilterIfNotExists(filterActionMoveDecrypt);
+ addFilterIfNotExists(filterActionCopyDecrypt);
+ addFilterIfNotExists(filterActionEncrypt);
+
+ gEnigmailFilters.onStartup();
+ },
+
+ onShutdown: function() {
+ gEnigmailFilters.onShutdown();
+ gEnigmailFilters = null;
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/fixExchangeMsg.jsm
@@ -0,0 +1,428 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailFixExchangeMsg"];
+
+const EnigmailCompat = ChromeUtils.import("chrome://openpgp/content/modules/compat.jsm").EnigmailCompat;
+const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
+const EnigmailFuncs = ChromeUtils.import("chrome://openpgp/content/modules/funcs.jsm").EnigmailFuncs;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailStreams = ChromeUtils.import("chrome://openpgp/content/modules/streams.jsm").EnigmailStreams;
+const EnigmailMime = ChromeUtils.import("chrome://openpgp/content/modules/mime.jsm").EnigmailMime;
+
+const IOSERVICE_CONTRACTID = "@mozilla.org/network/io-service;1";
+
+/*
+ * Fix a broken message from MS-Exchange and replace it with the original message
+ *
+ * @param nsIMsgDBHdr hdr Header of the message to fix (= pointer to message)
+ * @param String brokenByApp Type of app that created the message. Currently one of
+ * exchange, iPGMail
+ * @param String destFolderUri optional destination Folder URI
+ *
+ * @return Promise; upon success, the promise returns the messageKey
+ */
+var EnigmailFixExchangeMsg = {
+ fixExchangeMessage: function(hdr, brokenByApp, destFolderUri) {
+ var self = this;
+ return new Promise(
+ function fixExchangeMessage_p(resolve, reject) {
+
+ let msgUriSpec = hdr.folder.getUriForMsg(hdr);
+ EnigmailLog.DEBUG("fixExchangeMsg.jsm: fixExchangeMessage: msgUriSpec: " + msgUriSpec + "\n");
+
+ self.hdr = hdr;
+ self.destFolder = hdr.folder;
+ self.resolve = resolve;
+ self.reject = reject;
+ self.brokenByApp = brokenByApp;
+
+ if (destFolderUri) {
+ self.destFolder = EnigmailCompat.getExistingFolder(destFolderUri);
+ }
+
+
+ let messenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
+ self.msgSvc = messenger.messageServiceFromURI(msgUriSpec);
+
+ let p = self.getMessageBody();
+ p.then(
+ function resolved(fixedMsgData) {
+ EnigmailLog.DEBUG("fixExchangeMsg.jsm: fixExchangeMessage: got fixedMsgData\n");
+ if (self.checkMessageStructure(fixedMsgData)) {
+ self.copyToTargetFolder(fixedMsgData);
+ } else {
+ reject();
+ }
+ });
+ p.catch(
+ function rejected(reason) {
+ EnigmailLog.DEBUG("fixExchangeMsg.jsm: fixExchangeMessage: caught rejection: " + reason + "\n");
+ reject();
+ return;
+ });
+ }
+ );
+ },
+
+ getMessageBody: function() {
+ EnigmailLog.DEBUG("fixExchangeMsg.jsm: getMessageBody:\n");
+
+ var self = this;
+
+ return new Promise(
+ function(resolve, reject) {
+ let url = EnigmailCompat.getUrlFromUriSpec(self.hdr.folder.getUriForMsg(self.hdr));
+
+ EnigmailLog.DEBUG("fixExchangeMsg.jsm: getting data from URL " + url + "\n");
+
+ let s = EnigmailStreams.newStringStreamListener(
+ function analyzeData(data) {
+ EnigmailLog.DEBUG("fixExchangeMsg.jsm: analyzeDecryptedData: got " + data.length + " bytes\n");
+
+ if (EnigmailLog.getLogLevel() > 5) {
+ EnigmailLog.DEBUG("*** start data ***\n'" + data + "'\n***end data***\n");
+ }
+
+ try {
+ let msg = self.getRepairedMessage(data);
+
+ if (msg) {
+ resolve(msg);
+ } else
+ reject(2);
+ return;
+
+ } catch (ex) {
+ reject(ex);
+ }
+ }
+ );
+
+ var ioServ = Components.classes[IOSERVICE_CONTRACTID].getService(Components.interfaces.nsIIOService);
+ try {
+ let channel = EnigmailStreams.createChannel(url);
+ channel.asyncOpen(s, null);
+ } catch (e) {
+ EnigmailLog.DEBUG("fixExchangeMsg.jsm: getMessageBody: exception " + e + "\n");
+ }
+ }
+ );
+ },
+
+ getRepairedMessage: function(data) {
+ this.determineCreatorApp(data);
+
+ let hdrEnd = data.search(/\r?\n\r?\n/);
+
+ if (hdrEnd <= 0) {
+ // cannot find end of header data
+ throw 0;
+ }
+
+ let hdrLines = data.substr(0, hdrEnd).split(/\r?\n/);
+ let hdrObj = this.getFixedHeaderData(hdrLines);
+
+ if (hdrObj.headers.length === 0 || hdrObj.boundary.length === 0) {
+ throw 1;
+ }
+
+ let boundary = hdrObj.boundary;
+ let body;
+
+ switch (this.brokenByApp) {
+ case "exchange":
+ body = this.getCorrectedExchangeBodyData(data.substr(hdrEnd + 2), boundary);
+ break;
+ case "iPGMail":
+ body = this.getCorrectediPGMailBodyData(data.substr(hdrEnd + 2), boundary);
+ break;
+ default:
+ EnigmailLog.ERROR("fixExchangeMsg.jsm: getRepairedMessage: unknown appType " + self.brokenByApp + "\n");
+ throw 99;
+ }
+
+ if (body) {
+ return hdrObj.headers + "\r\n" + body;
+ } else {
+ throw 2;
+ }
+ },
+
+ determineCreatorApp: function(msgData) {
+ // perform extra testing if iPGMail is assumed
+ if (this.brokenByApp === "exchange") return;
+
+ let msgTree = EnigmailMime.getMimeTree(msgData, false);
+
+ try {
+ let isIPGMail =
+ msgTree.subParts.length === 3 &&
+ msgTree.subParts[0].headers.get("content-type").type.toLowerCase() === "text/plain" &&
+ msgTree.subParts[1].headers.get("content-type").type.toLowerCase() === "application/pgp-encrypted" &&
+ msgTree.subParts[2].headers.get("content-type").type.toLowerCase() === "text/plain";
+
+ if (!isIPGMail) {
+ this.brokenByApp = "exchange";
+ }
+ } catch (x) {}
+ },
+
+ /**
+ * repair header data, such that they are working for PGP/MIME
+ *
+ * @return: object: {
+ * headers: String - all headers ready for appending to message
+ * boundary: String - MIME part boundary (incl. surrounding "" or '')
+ * }
+ */
+ getFixedHeaderData: function(hdrLines) {
+ EnigmailLog.DEBUG("fixExchangeMsg.jsm: getFixedHeaderData: hdrLines[]:'" + hdrLines.length + "'\n");
+ let r = {
+ headers: "",
+ boundary: ""
+ };
+
+ for (let i = 0; i < hdrLines.length; i++) {
+ if (hdrLines[i].search(/^content-type:/i) >= 0) {
+ // Join the rest of the content type lines together.
+ // See RFC 2425, section 5.8.1
+ let contentTypeLine = hdrLines[i];
+ i++;
+ while (i < hdrLines.length) {
+ // Does the line start with a space or a tab, followed by something else?
+ if (hdrLines[i].search(/^[ \t]+?/) === 0) {
+ contentTypeLine += hdrLines[i];
+ i++;
+ } else {
+ // we got the complete content-type header
+ contentTypeLine = contentTypeLine.replace(/[\r\n]/g, "");
+ let h = EnigmailFuncs.getHeaderData(contentTypeLine);
+ r.boundary = h.boundary || "";
+ break;
+ }
+ }
+ } else {
+ r.headers += hdrLines[i] + "\r\n";
+ }
+ }
+
+ r.boundary = r.boundary.replace(/^(['"])(.*)(['"])/, "$2");
+
+ r.headers += 'Content-Type: multipart/encrypted;\r\n' +
+ ' protocol="application/pgp-encrypted";\r\n' +
+ ' boundary="' + r.boundary + '"\r\n' +
+ 'X-Enigmail-Info: Fixed broken PGP/MIME message\r\n';
+
+ return r;
+ },
+
+
+ /**
+ * Get corrected body for MS-Exchange messages
+ */
+ getCorrectedExchangeBodyData: function(bodyData, boundary) {
+ EnigmailLog.DEBUG("fixExchangeMsg.jsm: getCorrectedExchangeBodyData: boundary='" + boundary + "'\n");
+ let boundRx = new RegExp("^--" + boundary, "gm");
+ let match = boundRx.exec(bodyData);
+
+ if (match.index < 0) {
+ EnigmailLog.DEBUG("fixExchangeMsg.jsm: getCorrectedExchangeBodyData: did not find index of mime type to skip\n");
+ return null;
+ }
+
+ let skipStart = match.index;
+ // found first instance -- that's the message part to ignore
+ match = boundRx.exec(bodyData);
+ if (match.index <= 0) {
+ EnigmailLog.DEBUG("fixExchangeMsg.jsm: getCorrectedExchangeBodyData: did not find boundary of PGP/MIME version identification\n");
+ return null;
+ }
+
+ let versionIdent = match.index;
+
+ if (bodyData.substring(skipStart, versionIdent).search(/^content-type:[ \t]*text\/(plain|html)/mi) < 0) {
+ EnigmailLog.DEBUG("fixExchangeMsg.jsm: getCorrectedExchangeBodyData: first MIME part is not content-type text/plain or text/html\n");
+ return null;
+ }
+
+ match = boundRx.exec(bodyData);
+ if (match.index < 0) {
+ EnigmailLog.DEBUG("fixExchangeMsg.jsm: getCorrectedExchangeBodyData: did not find boundary of PGP/MIME encrypted data\n");
+ return null;
+ }
+
+ let encData = match.index;
+ let mimeHdr = Cc["@mozilla.org/messenger/mimeheaders;1"].createInstance(Ci.nsIMimeHeaders);
+ mimeHdr.initialize(bodyData.substring(versionIdent, encData));
+ let ct = mimeHdr.extractHeader("content-type", false);
+
+ if (!ct || ct.search(/application\/pgp-encrypted/i) < 0) {
+ EnigmailLog.DEBUG("fixExchangeMsg.jsm: getCorrectedExchangeBodyData: wrong content-type of version-identification\n");
+ EnigmailLog.DEBUG(" ct = '" + ct + "'\n");
+ return null;
+ }
+
+ mimeHdr.initialize(bodyData.substr(encData, 5000));
+ ct = mimeHdr.extractHeader("content-type", false);
+ if (!ct || ct.search(/application\/octet-stream/i) < 0) {
+ EnigmailLog.DEBUG("fixExchangeMsg.jsm: getCorrectedExchangeBodyData: wrong content-type of PGP/MIME data\n");
+ EnigmailLog.DEBUG(" ct = '" + ct + "'\n");
+ return null;
+ }
+
+ return bodyData.substr(versionIdent);
+ },
+
+
+ /**
+ * Get corrected body for iPGMail messages
+ */
+ getCorrectediPGMailBodyData: function(bodyData, boundary) {
+ EnigmailLog.DEBUG("fixExchangeMsg.jsm: getCorrectediPGMailBodyData: boundary='" + boundary + "'\n");
+ let boundRx = new RegExp("^--" + boundary, "gm");
+ let match = boundRx.exec(bodyData);
+
+ if (match.index < 0) {
+ EnigmailLog.DEBUG("fixExchangeMsg.jsm: getCorrectediPGMailBodyData: did not find index of mime type to skip\n");
+ return null;
+ }
+
+ let skipStart = match.index;
+ // found first instance -- that's the message part to ignore
+ match = boundRx.exec(bodyData);
+ if (match.index <= 0) {
+ EnigmailLog.DEBUG("fixExchangeMsg.jsm: getCorrectediPGMailBodyData: did not find boundary of text/plain msg part\n");
+ return null;
+ }
+
+ let encData = match.index;
+
+ match = boundRx.exec(bodyData);
+ if (match.index < 0) {
+ EnigmailLog.DEBUG("fixExchangeMsg.jsm: getCorrectediPGMailBodyData: did not find end boundary of PGP/MIME encrypted data\n");
+ return null;
+ }
+
+ let mimeHdr = Cc["@mozilla.org/messenger/mimeheaders;1"].createInstance(Ci.nsIMimeHeaders);
+
+ mimeHdr.initialize(bodyData.substr(encData, 5000));
+ let ct = mimeHdr.extractHeader("content-type", false);
+ if (!ct || ct.search(/application\/pgp-encrypted/i) < 0) {
+ EnigmailLog.DEBUG("fixExchangeMsg.jsm: getCorrectediPGMailBodyData: wrong content-type of PGP/MIME data\n");
+ EnigmailLog.DEBUG(" ct = '" + ct + "'\n");
+ return null;
+ }
+
+ return "--" + boundary + "\r\n" +
+ "Content-Type: application/pgp-encrypted\r\n" +
+ "Content-Description: PGP/MIME version identification\r\n\r\n" +
+ "Version: 1\r\n\r\n" +
+ bodyData.substring(encData, match.index).replace(/^Content-Type: +application\/pgp-encrypted/im,
+ "Content-Type: application/octet-stream") +
+ "--" + boundary + "--\r\n";
+ },
+
+ checkMessageStructure: function(msgData) {
+ let msgTree = EnigmailMime.getMimeTree(msgData, true);
+
+ try {
+
+ // check message structure
+ let ok =
+ msgTree.headers.get("content-type").type.toLowerCase() === "multipart/encrypted" &&
+ msgTree.headers.get("content-type").get("protocol").toLowerCase() === "application/pgp-encrypted" &&
+ msgTree.subParts.length === 2 &&
+ msgTree.subParts[0].headers.get("content-type").type.toLowerCase() === "application/pgp-encrypted" &&
+ msgTree.subParts[1].headers.get("content-type").type.toLowerCase() === "application/octet-stream";
+
+
+ if (ok) {
+ // check for existence of PGP Armor
+ let body = msgTree.subParts[1].body;
+ let p0 = body.search(/^-----BEGIN PGP MESSAGE-----$/m);
+ let p1 = body.search(/^-----END PGP MESSAGE-----$/m);
+
+ ok = (p0 >= 0 && p1 > p0 + 32);
+ }
+ return ok;
+ } catch (x) {}
+ return false;
+ },
+
+ copyToTargetFolder: function(msgData) {
+ var self = this;
+ var tempFile = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties).get("TmpD", Ci.nsIFile);
+ tempFile.append("message.eml");
+ tempFile.createUnique(0, 0o600);
+
+ // ensure that file gets deleted on exit, if something goes wrong ...
+ var extAppLauncher = Cc["@mozilla.org/mime;1"].getService(Ci.nsPIExternalAppLauncher);
+
+ var foStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
+ foStream.init(tempFile, 2, 0x200, false); // open as "write only"
+ foStream.write(msgData, msgData.length);
+ foStream.close();
+
+ extAppLauncher.deleteTemporaryFileOnExit(tempFile);
+
+ // note: nsIMsgFolder.copyFileMessage seems to have a bug on Windows, when
+ // the nsIFile has been already used by foStream (because of Windows lock system?), so we
+ // must initialize another nsIFile object, pointing to the temporary file
+ var fileSpec = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ fileSpec.initWithPath(tempFile.path);
+
+
+ var copyListener = {
+ QueryInterface: function(iid) {
+ if (iid.equals(Ci.nsIMsgCopyServiceListener) || iid.equals(Ci.nsISupports)) {
+ return this;
+ }
+ throw Components.results.NS_NOINTERFACE;
+ },
+ msgKey: null,
+ GetMessageId: function(messageId) {},
+ OnProgress: function(progress, progressMax) {},
+ OnStartCopy: function() {},
+ SetMessageKey: function(key) {
+ this.msgKey = key;
+ },
+ OnStopCopy: function(statusCode) {
+ if (statusCode !== 0) {
+ EnigmailLog.DEBUG("fixExchangeMsg.jsm: error copying message: " + statusCode + "\n");
+ try {
+ tempFile.remove(false);
+ } catch (ex) {
+ EnigmailLog.DEBUG("persistentCrypto.jsm: Could not delete temp file\n");
+ }
+ self.reject(3);
+ return;
+ }
+ EnigmailLog.DEBUG("fixExchangeMsg.jsm: copy complete\n");
+
+ EnigmailLog.DEBUG("fixExchangeMsg.jsm: deleting message key=" + self.hdr.messageKey + "\n");
+ let msgArray = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
+ msgArray.appendElement(self.hdr, false);
+
+ self.hdr.folder.deleteMessages(msgArray, null, true, false, null, false);
+ EnigmailLog.DEBUG("fixExchangeMsg.jsm: deleted original message\n");
+
+ try {
+ tempFile.remove(false);
+ } catch (ex) {
+ EnigmailLog.DEBUG("persistentCrypto.jsm: Could not delete temp file\n");
+ }
+ self.resolve(this.msgKey);
+ return;
+ }
+ };
+
+ EnigmailCompat.copyFileToMailFolder(fileSpec, this.destFolder, 0, this.hdr.flags, copyListener, null);
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/funcs.jsm
@@ -0,0 +1,505 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailFuncs"];
+
+/*
+ * Common Enigmail crypto-related GUI functionality
+ *
+ */
+
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs.jsm").EnigmailPrefs;
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
+
+var gTxtConverter = null;
+
+var EnigmailFuncs = {
+ /**
+ * get a list of plain email addresses without name or surrounding <>
+ * @param mailAddrs |string| - address-list encdoded in Unicode as specified in RFC 2822, 3.4
+ * separated by , or ;
+ *
+ * @return |string| - list of pure email addresses separated by ","
+ */
+ stripEmail: function(mailAddresses) {
+ // EnigmailLog.DEBUG("funcs.jsm: stripEmail(): mailAddresses=" + mailAddresses + "\n");
+
+ const SIMPLE = "[^<>,]+"; // RegExp for a simple email address (e.g. a@b.c)
+ const COMPLEX = "[^<>,]*<[^<>, ]+>"; // RegExp for an address containing <...> (e.g. Name <a@b.c>)
+ const MatchAddr = new RegExp("^(" + SIMPLE + "|" + COMPLEX + ")(," + SIMPLE + "|," + COMPLEX + ")*$");
+
+ let mailAddrs = mailAddresses;
+
+ let qStart, qEnd;
+ while ((qStart = mailAddrs.indexOf('"')) >= 0) {
+ qEnd = mailAddrs.indexOf('"', qStart + 1);
+ if (qEnd < 0) {
+ EnigmailLog.ERROR("funcs.jsm: stripEmail: Unmatched quote in mail address: '" + mailAddresses + "'\n");
+ throw Components.results.NS_ERROR_FAILURE;
+ }
+
+ mailAddrs = mailAddrs.substring(0, qStart) + mailAddrs.substring(qEnd + 1);
+ }
+
+ // replace any ";" by ","; remove leading/trailing ","
+ mailAddrs = mailAddrs.replace(/[,;]+/g, ",").replace(/^,/, "").replace(/,$/, "");
+
+ if (mailAddrs.length === 0) return "";
+
+ // having two <..> <..> in one email, or things like <a@b.c,><d@e.f> is an error
+ if (mailAddrs.search(MatchAddr) < 0) {
+ EnigmailLog.ERROR("funcs.jsm: stripEmail: Invalid <..> brackets in mail address: '" + mailAddresses + "'\n");
+ throw Components.results.NS_ERROR_FAILURE;
+ }
+
+ // We know that the "," and the < > are at the right places, thus we can split by ","
+ let addrList = mailAddrs.split(/,/);
+
+ for (let i in addrList) {
+ // Extract pure e-mail address list (strip out anything before angle brackets and any whitespace)
+ addrList[i] = addrList[i].replace(/^([^<>]*<)([^<>]+)(>)$/, "$2").replace(/\s/g, "");
+ }
+
+ // remove repeated, trailing and leading "," (again, as there may be empty addresses)
+ mailAddrs = addrList.join(",").replace(/,,/g, ",").replace(/^,/, "").replace(/,$/, "");
+
+ return mailAddrs;
+ },
+
+ /**
+ * get an array of email object (email, name) from an address string
+ * @param mailAddrs |string| - address-list as specified in RFC 2822, 3.4
+ * separated by ","; encoded according to RFC 2047
+ *
+ * @return |array| of msgIAddressObject
+ */
+ parseEmails: function(mailAddrs, encoded = true) {
+
+ try {
+ let hdr = Cc["@mozilla.org/messenger/headerparser;1"].createInstance(Ci.nsIMsgHeaderParser);
+ if (encoded) {
+ return hdr.parseEncodedHeader(mailAddrs, "utf-8");
+ }
+ return hdr.parseDecodedHeader(mailAddrs);
+ }
+ catch (ex) {}
+
+ return [];
+ },
+
+ /**
+ * Hide all menu entries and other XUL elements that are considered for
+ * advanced users. The XUL items must contain 'advanced="true"' or
+ * 'advanced="reverse"'.
+ *
+ * @obj: |object| - XUL tree element
+ * @attribute: |string| - attribute to set or remove (i.e. "hidden" or "collapsed")
+ * @dummy: |object| - anything
+ *
+ * no return value
+ */
+
+
+ collapseAdvanced: function(obj, attribute, dummy) {
+ EnigmailLog.DEBUG("funcs.jsm: collapseAdvanced:\n");
+
+ var advancedUser = EnigmailPrefs.getPref("advancedUser");
+
+ obj = obj.firstChild;
+ while (obj) {
+ if ("getAttribute" in obj) {
+ if (obj.getAttribute("advanced") == "true") {
+ if (advancedUser) {
+ obj.removeAttribute(attribute);
+ }
+ else {
+ obj.setAttribute(attribute, "true");
+ }
+ }
+ else if (obj.getAttribute("advanced") == "reverse") {
+ if (advancedUser) {
+ obj.setAttribute(attribute, "true");
+ }
+ else {
+ obj.removeAttribute(attribute);
+ }
+ }
+ }
+
+ obj = obj.nextSibling;
+ }
+ },
+
+ /**
+ * this function tries to mimic the Thunderbird plaintext viewer
+ *
+ * @plainTxt - |string| containing the plain text data
+ *
+ * @ return HTML markup to display mssage
+ */
+
+ formatPlaintextMsg: function(plainTxt) {
+ if (!gTxtConverter)
+ gTxtConverter = Cc["@mozilla.org/txttohtmlconv;1"].createInstance(Ci.mozITXTToHTMLConv);
+
+ var prefRoot = EnigmailPrefs.getPrefRoot();
+ var fontStyle = "";
+
+ // set the style stuff according to perferences
+
+ switch (prefRoot.getIntPref("mail.quoted_style")) {
+ case 1:
+ fontStyle = "font-weight: bold; ";
+ break;
+ case 2:
+ fontStyle = "font-style: italic; ";
+ break;
+ case 3:
+ fontStyle = "font-weight: bold; font-style: italic; ";
+ break;
+ }
+
+ switch (prefRoot.getIntPref("mail.quoted_size")) {
+ case 1:
+ fontStyle += "font-size: large; ";
+ break;
+ case 2:
+ fontStyle += "font-size: small; ";
+ break;
+ }
+
+ fontStyle += "color: " + prefRoot.getCharPref("mail.citation_color") + ";";
+
+ var convFlags = Ci.mozITXTToHTMLConv.kURLs;
+ if (prefRoot.getBoolPref("mail.display_glyph"))
+ convFlags |= Ci.mozITXTToHTMLConv.kGlyphSubstitution;
+ if (prefRoot.getBoolPref("mail.display_struct"))
+ convFlags |= Ci.mozITXTToHTMLConv.kStructPhrase;
+
+ // start processing the message
+
+ plainTxt = plainTxt.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
+ var lines = plainTxt.split(/\n/);
+ var oldCiteLevel = 0;
+ var citeLevel = 0;
+ var preface = "";
+ var logLineStart = {
+ value: 0
+ };
+ var isSignature = false;
+
+ for (var i = 0; i < lines.length; i++) {
+ preface = "";
+ oldCiteLevel = citeLevel;
+ if (lines[i].search(/^[> \t]*>$/) === 0)
+ lines[i] += " ";
+
+ citeLevel = gTxtConverter.citeLevelTXT(lines[i], logLineStart);
+
+ if (citeLevel > oldCiteLevel) {
+
+ preface = '</pre>';
+ for (let j = 0; j < citeLevel - oldCiteLevel; j++) {
+ preface += '<blockquote type="cite" style="' + fontStyle + '">';
+ }
+ preface += '<pre wrap="">\n';
+ }
+ else if (citeLevel < oldCiteLevel) {
+ preface = '</pre>';
+ for (let j = 0; j < oldCiteLevel - citeLevel; j++)
+ preface += "</blockquote>";
+
+ preface += '<pre wrap="">\n';
+ }
+
+ if (logLineStart.value > 0) {
+ preface += '<span class="moz-txt-citetags">' +
+ gTxtConverter.scanTXT(lines[i].substr(0, logLineStart.value), convFlags) +
+ '</span>';
+ }
+ else if (lines[i] == "-- ") {
+ preface += '<div class="moz-txt-sig">';
+ isSignature = true;
+ }
+ lines[i] = preface + gTxtConverter.scanTXT(lines[i].substr(logLineStart.value), convFlags);
+
+ }
+
+ var r = '<pre wrap="">' + lines.join("\n") + (isSignature ? '</div>' : '') + '</pre>';
+ //EnigmailLog.DEBUG("funcs.jsm: r='"+r+"'\n");
+ return r;
+ },
+
+
+ /**
+ * extract the data fields following a header.
+ * e.g. ContentType: xyz; Aa=b; cc=d
+ * @data: |string| containing a single header
+ *
+ * @return |array| of |arrays| containing pairs of aa/b and cc/d
+ */
+ getHeaderData: function(data) {
+ EnigmailLog.DEBUG("funcs.jsm: getHeaderData: " + data.substr(0, 100) + "\n");
+ var a = data.split(/\n/);
+ var res = [];
+ for (let i = 0; i < a.length; i++) {
+ if (a[i].length === 0) break;
+ let b = a[i].split(/;/);
+
+ // extract "abc = xyz" tuples
+ for (let j = 0; j < b.length; j++) {
+ let m = b[j].match(/^(\s*)([^=\s;]+)(\s*)(=)(\s*)(.*)(\s*)$/);
+ if (m) {
+ // m[2]: identifier / m[6]: data
+ res[m[2].toLowerCase()] = m[6].replace(/\s*$/, "");
+ EnigmailLog.DEBUG("funcs.jsm: getHeaderData: " + m[2].toLowerCase() + " = " + res[m[2].toLowerCase()] + "\n");
+ }
+ }
+ if (i === 0 && a[i].indexOf(";") < 0) break;
+ if (i > 0 && a[i].search(/^\s/) < 0) break;
+ }
+ return res;
+ },
+
+ /***
+ * Get the text for the encrypted subject (either configured by user or default)
+ */
+ getProtectedSubjectText: function() {
+ if (EnigmailPrefs.getPref("protectedSubjectText").length > 0) {
+ return EnigmailData.convertToUnicode(EnigmailPrefs.getPref("protectedSubjectText"), "utf-8");
+ }
+ else {
+ return "...";
+ }
+ },
+
+ cloneObj: function(orig) {
+ let newObj;
+
+ if (typeof orig !== "object" || orig === null || orig === undefined) {
+ return orig;
+ }
+
+ if ("clone" in orig && typeof orig.clone === "function") {
+ return orig.clone();
+ }
+
+ if (Array.isArray(orig) && orig.length > 0) {
+ newObj = [];
+ for (let i in orig) {
+ if (typeof orig[i] === "object") {
+ newObj.push(this.cloneObj(orig[i]));
+ }
+ else {
+ newObj.push(orig[i]);
+ }
+ }
+ }
+ else {
+ newObj = {};
+ for (let i in orig) {
+ if (typeof orig[i] === "object") {
+ newObj[i] = this.cloneObj(orig[i]);
+ }
+ else
+ newObj[i] = orig[i];
+ }
+ }
+
+ return newObj;
+ },
+
+ /**
+ * Compare two MIME part numbers to determine which of the two is earlier in the tree
+ * MIME part numbers have the structure "x.y.z...", e.g 1, 1.2, 2.3.1.4.5.1.2
+ *
+ * @param mime1, mime2 - String the two mime part numbers to compare.
+ *
+ * @return Number (one of -2, -1, 0, 1 , 2)
+ * - Negative number if mime1 is before mime2
+ * - Positive number if mime1 is after mime2
+ * - 0 if mime1 and mime2 are equal
+ * - if mime1 is a parent of mime2 the return value is -2
+ * - if mime2 is a parent of mime1 the return value is 2
+ *
+ * Throws an error if mime1 or mime2 do not comply to the required format
+ */
+ compareMimePartLevel: function(mime1, mime2) {
+ let s = new RegExp("^[0-9]+(\\.[0-9]+)*$");
+ if (mime1.search(s) < 0) throw "Invalid mime1";
+ if (mime2.search(s) < 0) throw "Invalid mime2";
+
+ let a1 = mime1.split(/\./);
+ let a2 = mime2.split(/\./);
+
+ for (let i = 0; i < Math.min(a1.length, a2.length); i++) {
+ if (Number(a1[i]) < Number(a2[i])) return -1;
+ if (Number(a1[i]) > Number(a2[i])) return 1;
+ }
+
+ if (a2.length > a1.length) return -2;
+ if (a2.length < a1.length) return 2;
+ return 0;
+ },
+
+
+ /**
+ * Determine the total number of certificates in the X.509 certificates store
+ *
+ * @return {Number}: number of Certificates
+ */
+ getNumOfX509Certs: function() {
+
+ let certDb = Cc["@mozilla.org/security/x509certdb;1"].getService(Ci.nsIX509CertDB);
+ let certs = certDb.getCerts();
+ let nCerts = 0;
+
+ if (certs) {
+ // FIXME: API Change: what should happen for TB 70 and newer?
+ let e = certs.getEnumerator();
+
+ while (e.hasMoreElements()) {
+ nCerts++;
+ e.getNext();
+ }
+ }
+
+ return nCerts;
+ },
+
+ /**
+ * Get the nsIMsgAccount associated with a given nsIMsgIdentity
+ */
+ getAccountForIdentity: function(identity) {
+ let accountManager = Cc["@mozilla.org/messenger/account-manager;1"].getService(Ci.nsIMsgAccountManager);
+
+ for (let acct = 0; acct < accountManager.accounts.length; acct++) {
+ let ac = accountManager.accounts.queryElementAt(acct, Ci.nsIMsgAccount);
+
+ for (let i = 0; i < ac.identities.length; i++) {
+ let id = ac.identities.queryElementAt(i, Ci.nsIMsgIdentity);
+ if (id.key === identity.key) {
+ return ac;
+ }
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Get the default identity of the default account
+ */
+ getDefaultIdentity: function() {
+ let accountManager = Cc["@mozilla.org/messenger/account-manager;1"].getService(Ci.nsIMsgAccountManager);
+
+ try {
+ let ac;
+ if (accountManager.defaultAccount) {
+ ac = accountManager.defaultAccount;
+ }
+ else {
+ for (let i = 0; i < accountManager.accounts.length; i++) {
+ ac = accountManager.accounts.queryElementAt(i, Ci.nsIMsgAccount);
+ if (ac.incomingServer.type === "imap" || ac.incomingServer.type === "pop3") break;
+ }
+ }
+
+ if (ac.defaultIdentity) {
+ return ac.defaultIdentity;
+ }
+ return ac.identities.queryElementAt(0, Ci.nsIMsgIdentity);
+ }
+ catch (x) {
+ return null;
+ }
+ },
+
+ /**
+ * Strip extended email parts such as "+xyz" from "abc+xyz@gmail.com" for known domains
+ * Currently supported domains: gmail.com, googlemail.com
+ */
+ getBaseEmail: function(emailAddr) {
+ return emailAddr.replace(/\+.{1,999}@(gmail|googlemail).com$/i, "");
+ },
+
+ /**
+ * Get a list of all own email addresses, taken from all identities
+ * and all reply-to addresses
+ */
+ getOwnEmailAddresses: function() {
+ let ownEmails = {};
+
+ let am = Cc["@mozilla.org/messenger/account-manager;1"].getService(Ci.nsIMsgAccountManager);
+
+ // Determine all sorts of own email addresses
+ for (let i = 0; i < am.allIdentities.length; i++) {
+ let id = am.allIdentities.queryElementAt(i, Ci.nsIMsgIdentity);
+ if (id.email && id.email.length > 0) ownEmails[this.getBaseEmail(id.email.toLowerCase())] = 1;
+ if (id.replyTo && id.replyTo.length > 0) {
+ try {
+ let replyEmails = this.stripEmail(id.replyTo).toLowerCase().split(/,/);
+ for (let j in replyEmails) {
+ ownEmails[this.getBaseEmail(replyEmails[j])] = 1;
+ }
+ }
+ catch (ex) {}
+ }
+ }
+
+ return ownEmails;
+ },
+
+ /**
+ * Determine the distinct number of non-self recipients of a message.
+ * Only To: and Cc: fields are considered.
+ */
+ getNumberOfRecipients: function(msgCompField) {
+ let recipients = {},
+ ownEmails = this.getOwnEmailAddresses();
+
+ let allAddr = (this.stripEmail(msgCompField.to) + "," + this.stripEmail(msgCompField.cc)).toLowerCase();
+ let emails = allAddr.split(/,+/);
+
+ for (let i = 0; i < emails.length; i++) {
+ let r = this.getBaseEmail(emails[i]);
+ if (r.length > 0 && !(r in ownEmails)) {
+ recipients[r] = 1;
+ }
+ }
+
+ let numRecipients = 0;
+ for (let i in recipients) {
+ ++numRecipients;
+ }
+
+ return numRecipients;
+ },
+
+ /**
+ * Synchronize a promise
+ */
+ syncPromise: function(promise) {
+ let inspector = Cc["@mozilla.org/jsinspector;1"].createInstance(Ci.nsIJSInspector);
+
+ let res = null;
+ let p = promise.then(gotResult => {
+ res = gotResult;
+ inspector.exitNestedEventLoop();
+ }).catch(gotResult => {
+ res = gotResult;
+ inspector.exitNestedEventLoop();
+ });
+
+ inspector.enterNestedEventLoop(0);
+
+ return res;
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/glodaUtils.jsm
@@ -0,0 +1,28 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/*
+ This module is a shim module to make it easier to load
+ GlodaUtils from the various potential sources
+*/
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["GlodaUtils"];
+
+var GlodaUtils = null;
+
+try {
+ // TB with omnijar
+ GlodaUtils = ChromeUtils.import("resource:///modules/gloda/utils.js").GlodaUtils;
+}
+catch (ex) {
+ // "old style" TB
+ GlodaUtils = ChromeUtils.import("resource://app/modules/gloda/utils.js").GlodaUtils;
+}
+
+// We don't define the exported symbol here - that is on purpose
+// The goal of this module is simply to simplify loading of the component
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/gpg.jsm
@@ -0,0 +1,391 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+const EXPORTED_SYMBOLS = ["EnigmailGpg"];
+
+
+
+
+
+const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+const EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs.jsm").EnigmailPrefs;
+const EnigmailExecution = ChromeUtils.import("chrome://openpgp/content/modules/execution.jsm").EnigmailExecution;
+const subprocess = ChromeUtils.import("chrome://openpgp/content/modules/subprocess.jsm").subprocess;
+const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
+const EnigmailOS = ChromeUtils.import("chrome://openpgp/content/modules/os.jsm").EnigmailOS;
+const EnigmailVersioning = ChromeUtils.import("chrome://openpgp/content/modules/versioning.jsm").EnigmailVersioning;
+const EnigmailLazy = ChromeUtils.import("chrome://openpgp/content/modules/lazy.jsm").EnigmailLazy;
+const getGpgAgent = EnigmailLazy.loader("enigmail/gpgAgent.jsm", "EnigmailGpgAgent");
+const getDialog = EnigmailLazy.loader("enigmail/dialog.jsm", "EnigmailDialog");
+
+const MINIMUM_GPG_VERSION = "2.0.14";
+const GPG_BATCH_OPT_LIST = ["--batch", "--no-tty", "--no-verbose", "--status-fd", "2"];
+
+function pushTrimmedStr(arr, str, splitStr) {
+ // Helper function for pushing a string without leading/trailing spaces
+ // to an array
+ str = str.replace(/^ */, "").replace(/ *$/, "");
+ if (str.length > 0) {
+ if (splitStr) {
+ const tmpArr = str.split(/[\t ]+/);
+ for (let i = 0; i < tmpArr.length; i++) {
+ arr.push(tmpArr[i]);
+ }
+ } else {
+ arr.push(str);
+ }
+ }
+ return (str.length > 0);
+}
+
+function getDirmngrTorStatus(exitCodeObj) {
+ const command = getGpgAgent().resolveToolPath("gpg-connect-agent");
+ if (command === null) {
+ return null;
+ }
+
+ const args = ["--dirmngr"];
+
+ EnigmailLog.CONSOLE("enigmail> " + EnigmailFiles.formatCmdLine(command, args) + "\n");
+
+ let stdout = "";
+ try {
+ exitCodeObj.value = subprocess.call({
+ command: command,
+ arguments: args,
+ environment: EnigmailCore.getEnvList(),
+ stdin: function(stdin) {
+ stdin.write("GETINFO tor\r\n");
+ stdin.write("bye\r\n");
+ stdin.write("\r\n");
+ stdin.close();
+ },
+ stdout: function(data) {
+ stdout += data;
+ }
+ }).wait();
+ } catch (ex) {
+ exitCodeObj.value = -1;
+ EnigmailLog.DEBUG("enigmail> DONE with FAILURE\n");
+ }
+
+ return stdout;
+}
+
+function dirmngrConfiguredWithTor() {
+ if (!EnigmailGpg.getGpgFeature("supports-dirmngr")) return false;
+
+ const exitCodeObj = {
+ value: null
+ };
+ const output = getDirmngrTorStatus(exitCodeObj);
+
+ if (output === null || exitCodeObj.value < 0) {
+ return false;
+ }
+ return output.match(/Tor mode is enabled/) !== null;
+}
+
+var EnigmailGpg = {
+ agentVersion: "",
+ _agentPath: null,
+
+ get agentPath() {
+ return this._agentPath;
+ },
+
+ setAgentPath: function(path) {
+ this._agentPath = path;
+ },
+
+ /**
+ * return the minimum version of GnuPG that is supported by Enigmail
+ */
+ getMinimumGpgVersion: function() {
+ return MINIMUM_GPG_VERSION;
+ },
+
+ /***
+ determine if a specific feature is available in the GnuPG version used
+
+ @param featureName: String; one of the following values:
+ version-supported - is the gpg version supported at all (true for gpg >= 2.0.10)
+ supports-gpg-agent - is gpg-agent is auto-started (true for gpg >= 2.0.16)
+ keygen-passphrase - can the passphrase be specified when generating keys (false for gpg 2.1 and 2.1.1)
+ windows-photoid-bug - is there a bug in gpg with the output of photoid on Windows (true for gpg < 2.0.16)
+ genkey-no-protection - is "%no-protection" supported for generting keys (true for gpg >= 2.1)
+ search-keys-cmd - what command to use to terminate the --search-key operation. ("save" for gpg > 2.1; "quit" otherwise)
+ socks-on-windows - is SOCKS proxy supported on Windows (true for gpg >= 2.0.20)
+ supports-dirmngr - is dirmngr supported (true for gpg >= 2.1)
+ supports-ecc-keys - are ECC (elliptic curve) keys supported (true for gpg >= 2.1)
+ supports-sender - does gnupg understand the --sender argument (true for gpg >= 2.1.15)
+ supports-wkd - does gpg support wkd (web key directory) (true for gpg >= 2.1.19)
+ export-result - does gpg print EXPORTED when exporting keys (true for gpg >= 2.1.10)
+ decryption-info - does gpg print DECRYPTION_INFO (true for gpg >= 2.0.19)
+ export-specific-uid - does gpg support exporting a key with a specific UID (true for gpg >= 2.2.8)
+ supports-show-only - does gpg support --import-options show-only (true for gpg >= 2.1.14)
+ handles-huge-keys - can gpg deal with huge keys without aborting (true for gpg >= 2.2.17)
+
+ @return: depending on featureName - Boolean unless specified differently:
+ (true if feature is available / false otherwise)
+ If the feature cannot be found, undefined is returned
+ */
+ getGpgFeature: function(featureName) {
+ let gpgVersion = EnigmailGpg.agentVersion;
+
+ if (!gpgVersion || typeof(gpgVersion) != "string" || gpgVersion.length === 0) {
+ return undefined;
+ }
+
+ gpgVersion = gpgVersion.replace(/-.*$/, "");
+ if (gpgVersion.search(/^\d+\.\d+/) < 0) {
+ // not a valid version number
+ return undefined;
+ }
+
+ switch (featureName) {
+ case "version-supported":
+ return EnigmailVersioning.greaterThanOrEqual(gpgVersion, MINIMUM_GPG_VERSION);
+ case "supports-gpg-agent":
+ return EnigmailVersioning.greaterThanOrEqual(gpgVersion, "2.0.16");
+ case "keygen-passphrase":
+ return EnigmailVersioning.lessThan(gpgVersion, "2.1") || EnigmailVersioning.greaterThanOrEqual(gpgVersion, "2.1.2");
+ case "genkey-no-protection":
+ return EnigmailVersioning.greaterThan(gpgVersion, "2.1");
+ case "windows-photoid-bug":
+ return EnigmailVersioning.lessThan(gpgVersion, "2.0.16");
+ case "supports-dirmngr":
+ return EnigmailVersioning.greaterThan(gpgVersion, "2.1");
+ case "supports-ecc-keys":
+ return EnigmailVersioning.greaterThan(gpgVersion, "2.1");
+ case "socks-on-windows":
+ return EnigmailVersioning.greaterThanOrEqual(gpgVersion, "2.0.20");
+ case "search-keys-cmd":
+ // returns a string
+ if (EnigmailVersioning.greaterThan(gpgVersion, "2.1")) {
+ return "save";
+ } else
+ return "quit";
+ case "supports-sender":
+ return EnigmailVersioning.greaterThanOrEqual(gpgVersion, "2.1.15");
+ case "export-result":
+ return EnigmailVersioning.greaterThanOrEqual(gpgVersion, "2.1.10");
+ case "decryption-info":
+ return EnigmailVersioning.greaterThanOrEqual(gpgVersion, "2.0.19");
+ case "supports-wkd":
+ return EnigmailVersioning.greaterThanOrEqual(gpgVersion, "2.1.19");
+ case "export-specific-uid":
+ return EnigmailVersioning.greaterThanOrEqual(gpgVersion, "2.2.9");
+ case "supports-show-only":
+ return EnigmailVersioning.greaterThanOrEqual(gpgVersion, "2.1.14");
+ case "handles-huge-keys":
+ return EnigmailVersioning.greaterThanOrEqual(gpgVersion, "2.2.17");
+ }
+
+ return undefined;
+ },
+
+ /**
+ * get the standard arguments to pass to every GnuPG subprocess
+ *
+ * @withBatchOpts: Boolean - true: use --batch and some more options
+ * false: don't use --batch and co.
+ *
+ * @return: Array of String - the list of arguments
+ */
+ getStandardArgs: function(withBatchOpts) {
+ // return the arguments to pass to every GnuPG subprocess
+ let r = ["--charset", "utf-8", "--display-charset", "utf-8", "--no-auto-check-trustdb"]; // mandatory parameters to add in all cases
+
+ try {
+ let p = EnigmailPrefs.getPref("agentAdditionalParam").replace(/\\\\/g, "\\");
+
+ let i = 0;
+ let last = 0;
+ let foundSign = "";
+ let startQuote = -1;
+
+ while ((i = p.substr(last).search(/['"]/)) >= 0) {
+ if (startQuote == -1) {
+ startQuote = i;
+ foundSign = p.substr(last).charAt(i);
+ last = i + 1;
+ } else if (p.substr(last).charAt(i) == foundSign) {
+ // found enquoted part
+ if (startQuote > 1) pushTrimmedStr(r, p.substr(0, startQuote), true);
+
+ pushTrimmedStr(r, p.substr(startQuote + 1, last + i - startQuote - 1), false);
+ p = p.substr(last + i + 1);
+ last = 0;
+ startQuote = -1;
+ foundSign = "";
+ } else {
+ last = last + i + 1;
+ }
+ }
+
+ pushTrimmedStr(r, p, true);
+ } catch (ex) {}
+
+
+ if (withBatchOpts) {
+ r = r.concat(GPG_BATCH_OPT_LIST);
+ }
+
+ return r;
+ },
+
+ // returns the output of --with-colons --list-config
+ getGnupgConfig: function(exitCodeObj, errorMsgObj) {
+ if (!EnigmailGpg.agentPath) {
+ exitCodeObj.value = 0;
+ return "";
+ }
+
+ const args = EnigmailGpg.getStandardArgs(true).
+ concat(["--fixed-list-mode", "--with-colons", "--list-config"]);
+
+ const statusMsgObj = {};
+ const cmdErrorMsgObj = {};
+ const statusFlagsObj = {};
+
+ const listText = EnigmailExecution.execCmd(EnigmailGpg.agentPath, args, "", exitCodeObj, statusFlagsObj, statusMsgObj, cmdErrorMsgObj);
+
+ if (exitCodeObj.value !== 0) {
+ errorMsgObj.value = EnigmailLocale.getString("badCommand");
+ if (cmdErrorMsgObj.value) {
+ errorMsgObj.value += "\n" + EnigmailFiles.formatCmdLine(EnigmailGpg.agentPath, args);
+ errorMsgObj.value += "\n" + cmdErrorMsgObj.value;
+ }
+
+ return "";
+ }
+
+ return listText.replace(/(\r\n|\r)/g, "\n");
+ },
+
+ /**
+ * return an array containing the aliases and the email addresses
+ * of groups defined in gpg.conf
+ *
+ * @return: array of objects with the following properties:
+ * - alias: group name as used by GnuPG
+ * - keylist: list of keys (any form that GnuPG accepts), separated by ";"
+ *
+ * (see docu for gnupg parameter --group)
+ */
+ getGpgGroups: function() {
+ const exitCodeObj = {};
+ const errorMsgObj = {};
+
+ const cfgStr = EnigmailGpg.getGnupgConfig(exitCodeObj, errorMsgObj);
+
+ if (exitCodeObj.value !== 0) {
+ getDialog().alert(errorMsgObj.value);
+ return null;
+ }
+
+ const groups = [];
+ const cfg = cfgStr.split(/\n/);
+
+ for (let i = 0; i < cfg.length; i++) {
+ if (cfg[i].indexOf("cfg:group") === 0) {
+ const groupArr = cfg[i].split(/:/);
+ groups.push({
+ alias: groupArr[2],
+ keylist: groupArr[3]
+ });
+ }
+ }
+
+ return groups;
+ },
+
+ /**
+ * Force GnuPG to recalculate the trust db. This is sometimes required after importing keys.
+ *
+ * no return value
+ */
+ recalcTrustDb: function() {
+ EnigmailLog.DEBUG("enigmailCommon.jsm: recalcTrustDb:\n");
+
+ const command = EnigmailGpg.agentPath;
+ const args = EnigmailGpg.getStandardArgs(false).
+ concat(["--check-trustdb"]);
+
+ try {
+ const proc = subprocess.call({
+ command: EnigmailGpg.agentPath,
+ arguments: args,
+ environment: EnigmailCore.getEnvList(),
+ charset: null,
+ mergeStderr: false
+ });
+ proc.wait();
+ } catch (ex) {
+ EnigmailLog.ERROR("enigmailCommon.jsm: recalcTrustDb: subprocess.call failed with '" + ex.toString() + "'\n");
+ throw ex;
+ }
+ },
+
+ signingAlgIdToString: function(id) {
+ // RFC 4880 Sec. 9.1, RFC 6637 Sec. 5 and draft-koch-eddsa-for-openpgp-03 Sec. 8
+ switch (parseInt(id, 10)) {
+ case 1:
+ case 2:
+ case 3:
+ return "RSA";
+ case 16:
+ return "Elgamal";
+ case 17:
+ return "DSA";
+ case 18:
+ return "ECDH";
+ case 19:
+ return "ECDSA";
+ case 20:
+ return "ELG";
+ case 22:
+ return "EDDSA";
+ default:
+ return EnigmailLocale.getString("unknownSigningAlg", [parseInt(id, 10)]);
+ }
+ },
+
+ hashAlgIdToString: function(id) {
+ // RFC 4880 Sec. 9.4
+ switch (parseInt(id, 10)) {
+ case 1:
+ return "MD5";
+ case 2:
+ return "SHA-1";
+ case 3:
+ return "RIPE-MD/160";
+ case 8:
+ return "SHA256";
+ case 9:
+ return "SHA384";
+ case 10:
+ return "SHA512";
+ case 11:
+ return "SHA224";
+ default:
+ return EnigmailLocale.getString("unknownHashAlg", [parseInt(id, 10)]);
+ }
+ },
+
+ /**
+ * For versions of GPG 2.1 and higher, checks to see if the dirmngr is configured to use Tor
+ *
+ * @return Boolean - True if dirmngr is configured with Tor. False otherwise
+ */
+ dirmngrConfiguredWithTor: dirmngrConfiguredWithTor
+};
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/gpgAgent.jsm
@@ -0,0 +1,780 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailGpgAgent"];
+
+
+
+const ctypes = ChromeUtils.import("resource://gre/modules/ctypes.jsm").ctypes;
+const subprocess = ChromeUtils.import("chrome://openpgp/content/modules/subprocess.jsm").subprocess;
+const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
+const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs.jsm").EnigmailPrefs;
+const EnigmailOS = ChromeUtils.import("chrome://openpgp/content/modules/os.jsm").EnigmailOS;
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+const EnigmailWindows = ChromeUtils.import("chrome://openpgp/content/modules/windows.jsm").EnigmailWindows;
+const EnigmailApp = ChromeUtils.import("chrome://openpgp/content/modules/app.jsm").EnigmailApp;
+const EnigmailExecution = ChromeUtils.import("chrome://openpgp/content/modules/execution.jsm").EnigmailExecution;
+const EnigmailPassword = ChromeUtils.import("chrome://openpgp/content/modules/passwords.jsm").EnigmailPassword;
+const EnigmailSystem = ChromeUtils.import("chrome://openpgp/content/modules/system.jsm").EnigmailSystem;
+const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
+const EnigmailLazy = ChromeUtils.import("chrome://openpgp/content/modules/lazy.jsm").EnigmailLazy;
+const getEnigmailGpg = EnigmailLazy.loader("enigmail/gpg.jsm", "EnigmailGpg");
+const getDialog = EnigmailLazy.loader("enigmail/dialog.jsm", "EnigmailDialog");
+
+
+
+
+
+const NS_LOCAL_FILE_CONTRACTID = "@mozilla.org/file/local;1";
+const DIR_SERV_CONTRACTID = "@mozilla.org/file/directory_service;1";
+const NS_LOCALFILEOUTPUTSTREAM_CONTRACTID = "@mozilla.org/network/file-output-stream;1";
+
+const DEFAULT_FILE_PERMS = 0o600;
+
+// Making this a var makes it possible to test windows things on linux
+var nsIWindowsRegKey = Ci.nsIWindowsRegKey;
+
+var gIsGpgAgent = -1;
+
+const DUMMY_AGENT_INFO = "none";
+
+function cloneOrNull(v) {
+ if (v && typeof v.clone === "function") {
+ return v.clone();
+ }
+ else {
+ return v;
+ }
+}
+
+function extractAgentInfo(fullStr) {
+ if (fullStr) {
+ return fullStr.
+ replace(/[\r\n]/g, "").
+ replace(/^.*=/, "").
+ replace(/;.*$/, "");
+ }
+ else {
+ return "";
+ }
+}
+
+function getHomedirFromParam(param) {
+ let i = param.search(/--homedir/);
+ if (i >= 0) {
+ param = param.substr(i + 9);
+
+ let m = param.match(/^(\s*)([^\\]".+[^\\]")/);
+ if (m && m.length > 2) {
+ param = m[2].substr(1);
+ let j = param.search(/[^\\]"/);
+ return param.substr(1, j);
+ }
+
+ m = param.match(/^(\s*)([^\\]'.+[^\\]')/);
+ if (m && m.length > 2) {
+ param = m[2].substr(1);
+ let j = param.search(/[^\\]'/);
+ return param.substr(1, j);
+ }
+
+ m = param.match(/^(\s*)(\S+)/);
+ if (m && m.length > 2) {
+ return m[2];
+ }
+ }
+
+ return null;
+}
+
+var EnigmailGpgAgent = {
+ agentType: "",
+ agentPath: null,
+ connGpgAgentPath: null,
+ gpgconfPath: null,
+ gpgAgentInfo: {
+ preStarted: false,
+ envStr: ""
+ },
+ gpgAgentProcess: null,
+ gpgAgentIsOptional: true,
+
+ isDummy: function() {
+ return EnigmailGpgAgent.gpgAgentInfo.envStr === DUMMY_AGENT_INFO;
+ },
+
+ resetGpgAgent: function() {
+ EnigmailLog.DEBUG("gpgAgent.jsm: resetGpgAgent\n");
+ gIsGpgAgent = -1;
+ },
+
+ isCmdGpgAgent: function(pid) {
+ EnigmailLog.DEBUG("gpgAgent.jsm: isCmdGpgAgent:\n");
+
+ const environment = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
+ let ret = false;
+
+ let path = environment.get("PATH");
+ if (!path || path.length === 0) {
+ path = "/bin:/usr/bin:/usr/local/bin";
+ }
+
+ const psCmd = EnigmailFiles.resolvePath("ps", path, false);
+ let outStr = "";
+
+ const proc = {
+ command: psCmd,
+ arguments: ["-o", "comm", "-p", pid],
+ environment: EnigmailCore.getEnvList(),
+ charset: null,
+ stdout: function(data) {
+ outStr += data;
+ }
+ };
+
+ try {
+ subprocess.call(proc).wait();
+
+ EnigmailLog.DEBUG("gpgAgent.jsm: isCmdGpgAgent: got data: '" + outStr + "'\n");
+ var data = outStr.replace(/[\r\n]/g, " ");
+ if (data.search(/gpg-agent/) >= 0) {
+ ret = true;
+ }
+ }
+ catch (ex) {}
+
+ return ret;
+
+ },
+
+ isAgentTypeGpgAgent: function() {
+ // determine if the used agent is a gpg-agent
+
+ EnigmailLog.DEBUG("gpgAgent.jsm: isAgentTypeGpgAgent:\n");
+
+ // to my knowledge there is no other agent than gpg-agent on Windows
+ if (EnigmailOS.getOS() == "WINNT") return true;
+
+ if (gIsGpgAgent >= 0) {
+ return gIsGpgAgent == 1;
+ }
+
+ let pid = -1;
+ let exitCode = -1;
+ let outStr = "";
+ if (!EnigmailCore.getService()) return false;
+
+ const proc = {
+ command: EnigmailGpgAgent.connGpgAgentPath,
+ arguments: [],
+ charset: null,
+ environment: EnigmailCore.getEnvList(),
+ stdin: function(pipe) {
+ pipe.write("/subst\n");
+ pipe.write("/serverpid\n");
+ pipe.write("/echo pid: ${get serverpid}\n");
+ pipe.write("/bye\n");
+ pipe.close();
+ },
+ stdout: function(data) {
+ outStr += data;
+ }
+ };
+
+ try {
+ exitCode = subprocess.call(proc).wait();
+ if (exitCode) pid = -2;
+
+ const data = outStr.replace(/[\r\n]/g, "");
+ if (data.search(/^pid: [0-9]+$/) === 0) {
+ pid = data.replace(/^pid: /, "");
+ }
+ }
+ catch (ex) {}
+
+ EnigmailLog.DEBUG("gpgAgent.jsm: isAgentTypeGpgAgent: pid=" + pid + "\n");
+
+ EnigmailGpgAgent.isCmdGpgAgent(pid);
+ let isAgent = false;
+
+ try {
+ isAgent = EnigmailGpgAgent.isCmdGpgAgent(pid);
+ gIsGpgAgent = isAgent ? 1 : 0;
+ }
+ catch (ex) {}
+
+ return isAgent;
+ },
+
+ getAgentMaxIdle: function() {
+ EnigmailLog.DEBUG("gpgAgent.jsm: getAgentMaxIdle:\n");
+ let maxIdle = -1;
+
+ if (!EnigmailCore.getService()) return maxIdle;
+
+ const DEFAULT = 7;
+ const CFGVALUE = 9;
+ let outStr = "";
+
+ const proc = {
+ command: EnigmailGpgAgent.gpgconfPath,
+ arguments: ["--list-options", "gpg-agent"],
+ charset: null,
+ environment: EnigmailCore.getEnvList(),
+ stdout: function(data) {
+ outStr += data;
+ }
+ };
+
+ subprocess.call(proc).wait();
+
+ const lines = outStr.split(/[\r\n]/);
+
+ for (let i = 0; i < lines.length; i++) {
+ EnigmailLog.DEBUG("gpgAgent.jsm: getAgentMaxIdle: line: " + lines[i] + "\n");
+
+ if (lines[i].search(/^default-cache-ttl:/) === 0) {
+ const m = lines[i].split(/:/);
+ if (m[CFGVALUE].length === 0) {
+ maxIdle = Math.round(m[DEFAULT] / 60);
+ }
+ else {
+ maxIdle = Math.round(m[CFGVALUE] / 60);
+ }
+
+ break;
+ }
+ }
+ return maxIdle;
+ },
+
+ setAgentMaxIdle: function(idleMinutes) {
+ EnigmailLog.DEBUG("gpgAgent.jsm: setAgentMaxIdle:\n");
+ if (!EnigmailCore.getService()) return;
+
+ const RUNTIME = 8;
+
+ const proc = {
+ command: EnigmailGpgAgent.gpgconfPath,
+ arguments: ["--runtime", "--change-options", "gpg-agent"],
+ environment: EnigmailCore.getEnvList(),
+ charset: null,
+ mergeStderr: true,
+ stdin: function(pipe) {
+ pipe.write("default-cache-ttl:" + RUNTIME + ":" + (idleMinutes * 60) + "\n");
+ pipe.write("max-cache-ttl:" + RUNTIME + ":" + (idleMinutes * 600) + "\n");
+ pipe.close();
+ },
+ stdout: function(data) {
+ EnigmailLog.DEBUG("gpgAgent.jsm: setAgentMaxIdle.stdout: " + data + "\n");
+ }
+ };
+
+ try {
+ let exitCode = subprocess.call(proc);
+ EnigmailLog.DEBUG("gpgAgent.jsm: setAgentMaxIdle.stdout: gpgconf exitCode=" + exitCode + "\n");
+ }
+ catch (ex) {
+ EnigmailLog.DEBUG("gpgAgent.jsm: setAgentMaxIdle: exception: " + ex.toString() + "\n");
+ }
+ },
+
+ getMaxIdlePref: function(win) {
+ let maxIdle = EnigmailPrefs.getPref("maxIdleMinutes");
+
+ try {
+ if (EnigmailCore.getService(win)) {
+ if (EnigmailGpgAgent.gpgconfPath &&
+ EnigmailGpgAgent.connGpgAgentPath) {
+
+ if (EnigmailGpgAgent.isAgentTypeGpgAgent()) {
+ const m = EnigmailGpgAgent.getAgentMaxIdle();
+ if (m > -1) maxIdle = m;
+ }
+ }
+ }
+ }
+ catch (ex) {}
+
+ return maxIdle;
+ },
+
+ setMaxIdlePref: function(minutes) {
+ EnigmailPrefs.setPref("maxIdleMinutes", minutes);
+
+ if (EnigmailGpgAgent.isAgentTypeGpgAgent()) {
+ try {
+ EnigmailGpgAgent.setAgentMaxIdle(minutes);
+ }
+ catch (ex) {}
+ }
+ },
+
+ /**
+ * Determine the "gpg home dir", i.e. the directory where gpg.conf and the keyring are
+ * stored using the "additional parameter" and gpgconf.
+ *
+ * @return String - directory name, or NULL (in case the command did not succeed)
+ */
+ getGpgHomeDir: function() {
+
+ let param = EnigmailPrefs.getPref("agentAdditionalParam");
+
+ if (param) {
+ let hd = getHomedirFromParam(param);
+
+ if (hd) return hd;
+ }
+
+ if (EnigmailGpgAgent.gpgconfPath === null) return null;
+
+ const command = EnigmailGpgAgent.gpgconfPath;
+ let args = ["--list-dirs"];
+
+ let exitCode = -1;
+ let outStr = "";
+ EnigmailLog.DEBUG("gpgAgent.jsm: getGpgHomeDir: calling subprocess with '" + command.path + "'\n");
+
+ EnigmailLog.CONSOLE("enigmail> " + EnigmailFiles.formatCmdLine(command, args) + "\n");
+
+ const proc = {
+ command: command,
+ arguments: args,
+ environment: EnigmailCore.getEnvList(),
+ charset: null,
+ stdout: function(data) {
+ outStr += data;
+ },
+ mergeStderr: false
+ };
+
+ try {
+ exitCode = subprocess.call(proc).wait();
+ }
+ catch (ex) {
+ EnigmailLog.ERROR("gpgAgent.jsm: getGpgHomeDir: subprocess.call failed with '" + ex.toString() + "'\n");
+ EnigmailLog.DEBUG(" enigmail> DONE with FAILURE\n");
+ throw ex;
+ }
+
+ let m = outStr.match(/^(homedir:)(.*)$/mi);
+ if (m && m.length > 2) {
+ return EnigmailData.convertGpgToUnicode(unescape(m[2]));
+ }
+
+ return null;
+ },
+
+ /**
+ * @param domWindow: Object - parent window, may be NULL
+ * @param esvc: Object - Enigmail service object
+ * @param preferredPath: String - try to use specific path to locate gpg
+ */
+ setAgentPath: function(domWindow, esvc, preferredPath) {
+ EnigmailLog.DEBUG("gpgAgent.jsm: setAgentPath()\n");
+ let agentPath = "";
+ try {
+ if (preferredPath) {
+ agentPath = preferredPath;
+ }
+ else {
+ agentPath = EnigmailPrefs.getPrefBranch().getCharPref("agentPath");
+ }
+ }
+ catch (ex) {}
+
+ var agentType = "gpg";
+ var agentName = "";
+
+ EnigmailGpgAgent.resetGpgAgent();
+
+ if (agentPath) {
+ // Locate GnuPG executable
+
+ // Append default .exe extension for DOS-Like systems, if needed
+ if (EnigmailOS.isDosLike && (agentPath.search(/\.\w+$/) < 0)) {
+ agentPath += ".exe";
+ }
+
+ try {
+ let pathDir = Cc[NS_LOCAL_FILE_CONTRACTID].createInstance(Ci.nsIFile);
+
+ if (!EnigmailFiles.isAbsolutePath(agentPath, EnigmailOS.isDosLike)) {
+ // path relative to Mozilla installation dir
+ const ds = Cc[DIR_SERV_CONTRACTID].getService();
+ const dsprops = ds.QueryInterface(Ci.nsIProperties);
+ pathDir = dsprops.get("CurProcD", Ci.nsIFile);
+
+ const dirs = agentPath.split(new RegExp(EnigmailOS.isDosLike ? "\\\\" : "/"));
+ for (let i = 0; i < dirs.length; i++) {
+ if (dirs[i] != ".") {
+ pathDir.append(dirs[i]);
+ }
+ }
+ if (pathDir.exists()) {
+ pathDir.normalize();
+ }
+ }
+ else {
+ // absolute path
+ EnigmailFiles.initPath(pathDir, agentPath);
+ }
+ if (!(pathDir.isFile() /* && pathDir.isExecutable()*/ )) {
+ throw Components.results.NS_ERROR_FAILURE;
+ }
+ agentPath = pathDir.QueryInterface(Ci.nsIFile);
+
+ }
+ catch (ex) {
+ esvc.initializationError = EnigmailLocale.getString("gpgNotFound", [agentPath]);
+ EnigmailLog.ERROR("gpgAgent.jsm: initialize: Error - " + esvc.initializationError + "\n");
+ throw Components.results.NS_ERROR_FAILURE;
+ }
+ }
+ else {
+ agentPath = this.resolveGpgPath(esvc.environment);
+ if (!agentPath) {
+ esvc.initializationError = EnigmailLocale.getString("gpgNotInPath");
+ EnigmailLog.ERROR("gpgAgent.jsm: Error - " + esvc.initializationError + "\n");
+ throw Components.results.NS_ERROR_FAILURE;
+ }
+ }
+
+ agentPath.normalize(); // replace a/../b with b
+ EnigmailLog.CONSOLE("EnigmailAgentPath=" + EnigmailFiles.getFilePathDesc(agentPath) + "\n\n");
+
+ EnigmailGpgAgent.agentType = agentType;
+ EnigmailGpgAgent.agentPath = agentPath;
+ getEnigmailGpg().setAgentPath(agentPath);
+ EnigmailExecution.agentType = agentType;
+
+ const command = agentPath;
+ let args = [];
+ if (agentType == "gpg") {
+ args = ["--batch", "--no-tty", "--charset", "utf-8", "--display-charset", "utf-8", "--version", "--version"];
+ }
+
+ let exitCode = -1;
+ let outStr = "";
+ let errStr = "";
+ EnigmailLog.DEBUG("gpgAgent.jsm: setAgentPath: calling subprocess with '" + command.path + "'\n");
+
+ EnigmailLog.CONSOLE("enigmail> " + EnigmailFiles.formatCmdLine(command, args) + "\n");
+
+ const proc = {
+ command: command,
+ arguments: args,
+ environment: EnigmailCore.getEnvList(),
+ charset: null,
+ stdout: function(data) {
+ outStr += data;
+ },
+ stderr: function(data) {
+ errStr += data;
+ },
+ mergeStderr: false
+ };
+
+ try {
+ exitCode = subprocess.call(proc).wait();
+ }
+ catch (ex) {
+ EnigmailLog.ERROR("gpgAgent.jsm: setAgentPath: subprocess.call failed with '" + ex.toString() + "'\n");
+ EnigmailLog.DEBUG(" enigmail> DONE with FAILURE\n");
+ throw ex;
+ }
+ EnigmailLog.DEBUG(" enigmail> DONE\n");
+
+ outStr = EnigmailSystem.convertNativeToUnicode(outStr);
+
+ if (exitCode !== 0) {
+ EnigmailLog.ERROR("gpgAgent.jsm: setAgentPath: gpg failed with exitCode " + exitCode + " msg='" + outStr + " " + errStr + "'\n");
+ throw Components.results.NS_ERROR_FAILURE;
+ }
+
+ EnigmailLog.CONSOLE(outStr + "\n");
+
+ // detection for Gpg4Win wrapper
+ if (outStr.search(/^gpgwrap.*;/) === 0) {
+ const outLines = outStr.split(/[\n\r]+/);
+ const firstLine = outLines[0];
+ outLines.splice(0, 1);
+ outStr = outLines.join("\n");
+ agentPath = firstLine.replace(/^.*;[ \t]*/, "");
+
+ EnigmailLog.CONSOLE("gpg4win-gpgwrapper detected; EnigmailAgentPath=" + agentPath + "\n\n");
+ }
+
+ const versionParts = outStr.replace(/[\r\n].*/g, "").replace(/ *\(gpg4win.*\)/i, "").split(/ /);
+ const gpgVersion = versionParts[versionParts.length - 1];
+
+ EnigmailLog.DEBUG("gpgAgent.jsm: detected GnuPG version '" + gpgVersion + "'\n");
+ getEnigmailGpg().agentVersion = gpgVersion;
+
+ if (!getEnigmailGpg().getGpgFeature("version-supported")) {
+ if (!domWindow) {
+ domWindow = EnigmailWindows.getBestParentWin();
+ }
+ getDialog().alert(domWindow, EnigmailLocale.getString("oldGpgVersion20", [gpgVersion, getEnigmailGpg().getMinimumGpgVersion()]));
+ throw Components.results.NS_ERROR_FAILURE;
+ }
+
+ EnigmailGpgAgent.gpgconfPath = EnigmailGpgAgent.resolveToolPath("gpgconf");
+ EnigmailGpgAgent.connGpgAgentPath = EnigmailGpgAgent.resolveToolPath("gpg-connect-agent");
+
+ EnigmailGpgAgent.checkGpgHomeDir(domWindow, esvc);
+
+ EnigmailLog.DEBUG("gpgAgent.jsm: setAgentPath: gpgconf found: " + (EnigmailGpgAgent.gpgconfPath ? "yes" : "no") + "\n");
+ },
+
+
+ /**
+ * Determine the location of the GnuPG executable
+ *
+ * @param env: Object: nsIEnvironment to use
+ *
+ * @return Object: nsIFile pointing to gpg, or NULL
+ */
+ resolveGpgPath: function(env) {
+ EnigmailLog.DEBUG("gpgAgent.jsm: resolveGpgPath()\n");
+
+ let agentName = "";
+ if (EnigmailOS.isDosLike) {
+ agentName = "gpg2.exe;gpg.exe";
+ }
+ else {
+ agentName = "gpg2;gpg";
+ }
+
+ // Resolve relative path using PATH environment variable
+ const envPath = env.get("PATH");
+ let agentPath = EnigmailFiles.resolvePath(agentName, envPath, EnigmailOS.isDosLike);
+
+ if (!agentPath && EnigmailOS.isDosLike) {
+ // DOS-like systems: search for GPG in c:\gnupg, c:\gnupg\bin, d:\gnupg, d:\gnupg\bin
+ let gpgPath = "c:\\gnupg;c:\\gnupg\\bin;d:\\gnupg;d:\\gnupg\\bin";
+ agentPath = EnigmailFiles.resolvePath(agentName, gpgPath, EnigmailOS.isDosLike);
+ }
+
+ if ((!agentPath) && EnigmailOS.isWin32) {
+ // Look up in Windows Registry
+ const installDir = ["Software\\GNU\\GNUPG", "Software\\GNUPG"];
+
+ try {
+ for (let i = 0; i < installDir.length && !agentPath; i++) {
+ let gpgPath = EnigmailOS.getWinRegistryString(installDir[i], "Install Directory", nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE);
+
+ agentPath = EnigmailFiles.resolvePath(agentName, gpgPath, EnigmailOS.isDosLike());
+ if (!agentPath) {
+ gpgPath += "\\bin";
+ agentPath = EnigmailFiles.resolvePath(agentName, gpgPath, EnigmailOS.isDosLike());
+ }
+ }
+ }
+ catch (ex) {}
+
+ if (!agentPath) {
+ // try to determine the default PATH from the registry after the installation
+ // if we could not get any information from the registry
+ try {
+ let winPath = EnigmailOS.getWinRegistryString("SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment", "Path", nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE);
+ agentPath = EnigmailFiles.resolvePath(agentName, winPath, EnigmailOS.isDosLike);
+ }
+ catch (ex) {}
+ }
+
+ if (!agentPath) {
+ // default for gpg4win 3.0
+ let gpgPath = "C:\\Program Files\\GnuPG\\bin;C:\\Program Files (x86)\\GnuPG\\bin";
+ agentPath = EnigmailFiles.resolvePath(agentName, gpgPath, EnigmailOS.isDosLike);
+ }
+ }
+
+ if (!agentPath && !EnigmailOS.isDosLike) {
+ // Unix-like systems: check /usr/bin and /usr/local/bin
+ let gpgPath = "/usr/bin:/usr/local/bin";
+ agentPath = EnigmailFiles.resolvePath(agentName, gpgPath, EnigmailOS.isDosLike);
+ }
+
+ if (!agentPath) {
+ return null;
+ }
+
+ return agentPath.QueryInterface(Ci.nsIFile);
+ },
+
+ // resolve the path for GnuPG helper tools
+ resolveToolPath: function(fileName) {
+ let filePath = cloneOrNull(EnigmailGpgAgent.agentPath);
+
+ if (filePath) {
+ // try to get the install directory of gpg/gpg2 executable
+ filePath.normalize();
+ filePath = filePath.parent;
+ }
+
+ if (filePath) {
+ filePath.append(EnigmailFiles.potentialWindowsExecutable(fileName));
+ if (filePath.exists()) {
+ filePath.normalize();
+ return filePath;
+ }
+ }
+
+ return EnigmailFiles.resolvePathWithEnv(fileName);
+ },
+
+ detectGpgAgent: function(domWindow, esvc) {
+ EnigmailLog.DEBUG("gpgAgent.jsm: detectGpgAgent\n");
+
+ var gpgAgentInfo = esvc.environment.get("GPG_AGENT_INFO");
+ if (gpgAgentInfo && gpgAgentInfo.length > 0) {
+ EnigmailLog.DEBUG("gpgAgent.jsm: detectGpgAgent: GPG_AGENT_INFO variable available\n");
+ // env. variable suggests running gpg-agent
+ EnigmailGpgAgent.gpgAgentInfo.preStarted = true;
+ EnigmailGpgAgent.gpgAgentInfo.envStr = gpgAgentInfo;
+ EnigmailGpgAgent.gpgAgentIsOptional = false;
+ }
+ else {
+ EnigmailLog.DEBUG("gpgAgent.jsm: detectGpgAgent: no GPG_AGENT_INFO variable set\n");
+ EnigmailGpgAgent.gpgAgentInfo.preStarted = false;
+
+ if (!getEnigmailGpg().getGpgFeature("supports-gpg-agent")) {
+ esvc.initializationError = EnigmailLocale.getString("gpgAgent.noAutostart", getEnigmailGpg().agentVersion);
+ EnigmailLog.ERROR("gpgAgent.jsm: Error - " + esvc.initializationError + "\n");
+ throw Components.results.NS_ERROR_FAILURE;
+ }
+
+ var command = null;
+ var outStr = "";
+ var errorStr = "";
+ var exitCode = -1;
+ EnigmailGpgAgent.gpgAgentIsOptional = false;
+
+
+ EnigmailGpgAgent.gpgAgentInfo.envStr = DUMMY_AGENT_INFO;
+ var envFile = Components.classes[NS_LOCAL_FILE_CONTRACTID].createInstance(Ci.nsIFile);
+ EnigmailFiles.initPath(envFile, EnigmailGpgAgent.determineGpgHomeDir(esvc));
+ envFile.append("gpg-agent.conf");
+
+ if (!envFile.exists()) {
+ EnigmailLog.DEBUG("gpgAgent.jsm: detectGpgAgent: writing gpg-agent.conf file\n");
+ let data = "default-cache-ttl " + (EnigmailPassword.getMaxIdleMinutes() * 60) + "\n";
+ data += "max-cache-ttl 999999\n";
+ try {
+ var flags = 0x02 | 0x08 | 0x20;
+ var fileOutStream = Cc[NS_LOCALFILEOUTPUTSTREAM_CONTRACTID].createInstance(Ci.nsIFileOutputStream);
+ fileOutStream.init(envFile, flags, 384, 0); // 0600
+ fileOutStream.write(data, data.length);
+ fileOutStream.flush();
+ fileOutStream.close();
+ }
+ catch (ex) {} // ignore file write errors
+ }
+
+ }
+ EnigmailLog.DEBUG("gpgAgent.jsm: detectGpgAgent: GPG_AGENT_INFO='" + EnigmailGpgAgent.gpgAgentInfo.envStr + "'\n");
+ },
+
+ /**
+ * Determine the GnuPG home directory based on the same logic as GnuPG, but without involving
+ * any external tool.
+ *
+ * @return String - the path to the gpg home directory
+ */
+ determineGpgHomeDir: function(esvc) {
+
+ let param = EnigmailPrefs.getPref("agentAdditionalParam");
+ if (param) {
+ let hd = getHomedirFromParam(param);
+
+ if (hd) return hd;
+ }
+
+ let homeDir = esvc.environment.get("GNUPGHOME");
+
+ if (!homeDir && EnigmailOS.isWin32) {
+ homeDir = EnigmailOS.getWinRegistryString("Software\\GNU\\GNUPG", "HomeDir", nsIWindowsRegKey.ROOT_KEY_CURRENT_USER);
+
+ if (!homeDir) {
+ homeDir = esvc.environment.get("USERPROFILE") || esvc.environment.get("SystemRoot");
+
+ if (homeDir) homeDir += "\\Application Data\\GnuPG";
+ }
+
+ if (!homeDir) homeDir = "C:\\gnupg";
+ }
+
+ if (!homeDir) homeDir = esvc.environment.get("HOME") + "/.gnupg";
+
+ return homeDir;
+ },
+
+ /**
+ * Check if the users directory for GnuPG exists and is writeable.
+ * Throw exception if directory cannot be created or adjusted.
+ */
+ checkGpgHomeDir: function(domWindow, esvc) {
+ EnigmailLog.DEBUG("gpgAgent.jsm: checkGpgHomeDir:\n");
+
+ let homeDir = EnigmailGpgAgent.getGpgHomeDir();
+ if (!homeDir) homeDir = EnigmailGpgAgent.determineGpgHomeDir(esvc);
+
+ EnigmailLog.DEBUG("gpgAgent.jsm: checkGpgHomeDir: got homedir = '" + homeDir + "'\n");
+
+ let homeDirObj = Components.classes[NS_LOCAL_FILE_CONTRACTID].createInstance(Ci.nsIFile);
+ EnigmailFiles.initPath(homeDirObj, homeDir);
+
+ if (homeDirObj.exists()) {
+ homeDirObj.normalize(); // resolve symlinks etc.
+ }
+
+ let dirType = EnigmailFiles.ensureWritableDirectory(homeDirObj, 0x1C0); // 0700
+ let errMsg = "";
+ switch (dirType) {
+ case 1:
+ errMsg = "gpghomedir.notexists";
+ break;
+ case 2:
+ errMsg = "gpghomedir.notwritable";
+ break;
+ case 3:
+ errMsg = "gpghomedir.notdirectory";
+ break;
+ }
+
+ if (errMsg.length > 0) {
+ if (!domWindow) {
+ domWindow = EnigmailWindows.getBestParentWin();
+ }
+ getDialog().alert(domWindow, EnigmailLocale.getString(errMsg, homeDir) + "\n\n" + EnigmailLocale.getString("gpghomedir.notusable"));
+ throw Components.results.NS_ERROR_FAILURE;
+ }
+ },
+
+ finalize: function() {
+ if (EnigmailGpgAgent.gpgAgentProcess) {
+ EnigmailLog.DEBUG("gpgAgent.jsm: EnigmailGpgAgent.finalize: stopping gpg-agent\n");
+ try {
+ const proc = {
+ command: EnigmailGpgAgent.connGpgAgentPath,
+ arguments: ['killagent', '/bye'],
+ environment: EnigmailCore.getEnvList()
+ };
+
+ subprocess.call(proc).wait();
+ }
+ catch (ex) {
+ EnigmailLog.ERROR("gpgAgent.jsm: EnigmailGpgAgent.finalize ERROR: " + ex + "\n");
+ }
+ }
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/hash.jsm
@@ -0,0 +1,125 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailHash"];
+
+
+
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailWindows = ChromeUtils.import("chrome://openpgp/content/modules/windows.jsm").EnigmailWindows;
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+const EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs.jsm").EnigmailPrefs;
+const EnigmailEncryption = ChromeUtils.import("chrome://openpgp/content/modules/encryption.jsm").EnigmailEncryption;
+const EnigmailDialog = ChromeUtils.import("chrome://openpgp/content/modules/dialog.jsm").EnigmailDialog;
+const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
+
+
+
+const keyAlgorithms = [];
+const mimeHashAlgorithms = [null, "sha1", "ripemd160", "sha256", "sha384", "sha512", "sha224", "md5"];
+
+var EnigmailHash = {
+ determineAlgorithm: function(win, uiFlags, fromMailAddr, hashAlgoObj) {
+ EnigmailLog.DEBUG("hash.jsm: determineAlgorithm\n");
+
+ if (!win) {
+ win = EnigmailWindows.getMostRecentWindow();
+ }
+
+ const sendFlags = EnigmailConstants.SEND_TEST | EnigmailConstants.SEND_SIGNED;
+ const hashAlgo = mimeHashAlgorithms[EnigmailPrefs.getPref("mimeHashAlgorithm")];
+
+ if (typeof(keyAlgorithms[fromMailAddr]) != "string") {
+ // hash algorithm not yet known
+
+ const testUiFlags = EnigmailConstants.UI_TEST;
+ const listener = {
+ stdoutData: "",
+ stderrData: "",
+ exitCode: -1,
+ stdin: function(pipe) {
+ pipe.write("Dummy Test");
+ pipe.close();
+ },
+ stdout: function(data) {
+ this.stdoutData += data;
+ },
+ stderr: function(data) {
+ this.stderrData += data;
+ },
+ done: function(exitCode) {
+ this.exitCode = exitCode;
+ }
+ };
+
+ let errorMsgObj = {};
+ let statusFlagsObj = {};
+ const proc = EnigmailEncryption.encryptMessageStart(win, testUiFlags, fromMailAddr, "",
+ "", hashAlgo, sendFlags,
+ listener, statusFlagsObj, errorMsgObj);
+
+ if (!proc) {
+ hashAlgoObj.errorMsg = errorMsgObj.value;
+ hashAlgoObj.statusFlags = statusFlagsObj.value;
+ return 1;
+ }
+
+ proc.wait();
+
+ const msgText = listener.stdoutData;
+ const exitCode = listener.exitCode;
+
+ const retStatusObj = {};
+ let exitCode2 = EnigmailEncryption.encryptMessageEnd(fromMailAddr, listener.stderrData, exitCode,
+ testUiFlags, sendFlags, 10,
+ retStatusObj);
+
+ if ((exitCode2 === 0) && !msgText) exitCode2 = 1;
+ // if (exitCode2 > 0) exitCode2 = -exitCode2;
+
+ if (exitCode2 !== 0) {
+ // Abormal return
+ if (retStatusObj.statusFlags & EnigmailConstants.BAD_PASSPHRASE) {
+ // "Unremember" passphrase on error return
+ retStatusObj.errorMsg = EnigmailLocale.getString("badPhrase");
+ }
+ EnigmailDialog.alert(win, retStatusObj.errorMsg);
+ return exitCode2;
+ }
+
+ let hashAlgorithm = "sha1"; // default as defined in RFC 4880, section 7 is MD5 -- but that's outdated
+
+ const m = msgText.match(/^(Hash: )(.*)$/m);
+ if (m && (m.length > 2) && (m[1] == "Hash: ")) {
+ hashAlgorithm = m[2].toLowerCase();
+ }
+ else {
+ EnigmailLog.DEBUG("hash.jsm: determineAlgorithm: no hashAlgorithm specified - using MD5\n");
+ }
+
+ for (let i = 1; i < mimeHashAlgorithms.length; i++) {
+ if (mimeHashAlgorithms[i] === hashAlgorithm) {
+ EnigmailLog.DEBUG("hash.jsm: determineAlgorithm: found hashAlgorithm " + hashAlgorithm + "\n");
+ keyAlgorithms[fromMailAddr] = hashAlgorithm;
+ hashAlgoObj.value = hashAlgorithm;
+ return 0;
+ }
+ }
+
+ EnigmailLog.ERROR("hash.jsm: determineAlgorithm: no hashAlgorithm found\n");
+ return 2;
+ }
+ else {
+ EnigmailLog.DEBUG("hash.jsm: determineAlgorithm: hashAlgorithm " + keyAlgorithms[fromMailAddr] + " is cached\n");
+ hashAlgoObj.value = keyAlgorithms[fromMailAddr];
+ }
+
+ return 0;
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/httpProxy.jsm
@@ -0,0 +1,85 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailHttpProxy"];
+
+
+
+
+
+const EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs.jsm").EnigmailPrefs;
+
+const NS_PREFS_SERVICE_CID = "@mozilla.org/preferences-service;1";
+
+function getPasswdForHost(hostname, userObj, passwdObj) {
+ var loginmgr = Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager);
+
+ // search HTTP password 1st
+ var logins = loginmgr.findLogins({}, "http://" + hostname, "", "");
+ if (logins.length > 0) {
+ userObj.value = logins[0].username;
+ passwdObj.value = logins[0].password;
+ return true;
+ }
+
+ // look for any other password for same host
+ logins = loginmgr.getAllLogins({});
+ for (var i = 0; i < logins.lenth; i++) {
+ if (hostname == logins[i].hostname.replace(/^.*:\/\//, "")) {
+ userObj.value = logins[i].username;
+ passwdObj.value = logins[i].password;
+ return true;
+ }
+ }
+ return false;
+}
+
+var EnigmailHttpProxy = {
+ /**
+ * get Proxy for a given hostname as configured in Mozilla
+ *
+ * @hostname: String - the host to check if there is a proxy.
+ *
+ * @return: String - proxy host URL to provide to GnuPG
+ * null if no proxy required
+ */
+ getHttpProxy: function(hostName) {
+ var proxyHost = null;
+ if (((typeof hostName) !== 'undefined') && EnigmailPrefs.getPref("respectHttpProxy")) {
+ // determine proxy host
+ var prefsSvc = Cc[NS_PREFS_SERVICE_CID].getService(Ci.nsIPrefService);
+ var prefRoot = prefsSvc.getBranch(null);
+ var useProxy = prefRoot.getIntPref("network.proxy.type");
+ if (useProxy == 1) {
+ var proxyHostName = prefRoot.getCharPref("network.proxy.http");
+ var proxyHostPort = prefRoot.getIntPref("network.proxy.http_port");
+ var noProxy = prefRoot.getCharPref("network.proxy.no_proxies_on").split(/[ ,]/);
+ for (var i = 0; i < noProxy.length; i++) {
+ var proxySearch = new RegExp(noProxy[i].replace(/\./g, "\\.").replace(/\*/g, ".*") + "$", "i");
+ if (noProxy[i] && hostName.search(proxySearch) >= 0) {
+ i = noProxy.length + 1;
+ proxyHostName = null;
+ }
+ }
+
+ if (proxyHostName) {
+ var userObj = {};
+ var passwdObj = {};
+ if (getPasswdForHost(proxyHostName, userObj, passwdObj)) {
+ proxyHostName = userObj.value + ":" + passwdObj.value + "@" + proxyHostName;
+ }
+ }
+ if (proxyHostName && proxyHostPort) {
+ proxyHost = "http://" + proxyHostName + ":" + proxyHostPort;
+ }
+ }
+ }
+
+ return proxyHost;
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/key.jsm
@@ -0,0 +1,178 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailKey"];
+
+const KEY_BLOCK_UNKNOWN = 0;
+const KEY_BLOCK_KEY = 1;
+const KEY_BLOCK_REVOCATION = 2;
+
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailArmor = ChromeUtils.import("chrome://openpgp/content/modules/armor.jsm").EnigmailArmor;
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
+const EnigmailFuncs = ChromeUtils.import("chrome://openpgp/content/modules/funcs.jsm").EnigmailFuncs;
+const EnigmailLazy = ChromeUtils.import("chrome://openpgp/content/modules/lazy.jsm").EnigmailLazy;
+const getKeyRing = EnigmailLazy.loader("enigmail/keyRing.jsm", "EnigmailKeyRing");
+const getDialog = EnigmailLazy.loader("enigmail/dialog.jsm", "EnigmailDialog");
+const EnigmailCryptoAPI = ChromeUtils.import("chrome://openpgp/content/modules/cryptoAPI.jsm").EnigmailCryptoAPI;
+
+
+var EnigmailKey = {
+ /**
+ * Format a key fingerprint
+ * @fingerprint |string| - unformated OpenPGP fingerprint
+ *
+ * @return |string| - formatted string
+ */
+ formatFpr: function(fingerprint) {
+ //EnigmailLog.DEBUG("key.jsm: EnigmailKey.formatFpr(" + fingerprint + ")\n");
+ // format key fingerprint
+ let r = "";
+ const fpr = fingerprint.match(/(....)(....)(....)(....)(....)(....)(....)(....)(....)?(....)?/);
+ if (fpr && fpr.length > 2) {
+ fpr.shift();
+ r = fpr.join(" ");
+ }
+
+ return r;
+ },
+
+ // Extract public key from Status Message
+ extractPubkey: function(statusMsg) {
+ const matchb = statusMsg.match(/(^|\n)NO_PUBKEY (\w{8})(\w{8})/);
+ if (matchb && (matchb.length > 3)) {
+ EnigmailLog.DEBUG("enigmailCommon.jsm:: Enigmail.extractPubkey: NO_PUBKEY 0x" + matchb[3] + "\n");
+ return matchb[2] + matchb[3];
+ } else {
+ return null;
+ }
+ },
+
+ /**
+ * import a revocation certificate form a given keyblock string.
+ * Ask the user before importing the cert, and display an error
+ * message in case of failures.
+ */
+ importRevocationCert: function(keyId, keyBlockStr) {
+
+ let key = getKeyRing().getKeyById(keyId);
+
+ if (key) {
+ if (key.keyTrust === "r") {
+ // Key has already been revoked
+ getDialog().info(null, EnigmailLocale.getString("revokeKeyAlreadyRevoked", keyId));
+ } else {
+
+ let userId = key.userId + " - 0x" + key.keyId;
+ if (!getDialog().confirmDlg(null,
+ EnigmailLocale.getString("revokeKeyQuestion", userId),
+ EnigmailLocale.getString("keyMan.button.revokeKey"))) {
+ return;
+ }
+
+ let errorMsgObj = {};
+ if (getKeyRing().importKey(null, false, keyBlockStr, keyId, errorMsgObj) > 0) {
+ getDialog().alert(null, errorMsgObj.value);
+ }
+ }
+ } else {
+ // Suitable key for revocation certificate is not present in keyring
+ getDialog().alert(null, EnigmailLocale.getString("revokeKeyNotPresent", keyId));
+ }
+ },
+
+ /**
+ * Get details (key ID, UID) of the data contained in a OpenPGP key block
+ *
+ * @param keyBlockStr String: the contents of one or more public keys
+ * @param errorMsgObj Object: obj.value will contain an error message in case of failures
+ * @param interactive Boolean: if in interactive mode, may display dialogs (default: true)
+ *
+ * @return Array of objects with the following structure:
+ * - id (key ID)
+ * - fpr
+ * - name (the UID of the key)
+ * - state (one of "old" [existing key], "new" [new key], "invalid" [key cannot not be imported])
+ */
+ getKeyListFromKeyBlock: function(keyBlockStr, errorMsgObj, interactive = true) {
+ EnigmailLog.DEBUG("key.jsm: getKeyListFromKeyBlock\n");
+
+ const cApi = EnigmailCryptoAPI();
+ let keyList = [];
+ let key = {};
+ let blocks;
+ errorMsgObj.value = "";
+
+ try {
+ keyList = cApi.sync(cApi.getKeyListFromKeyBlock(keyBlockStr));
+ } catch (ex) {
+ errorMsgObj.value = ex.toString();
+ return [];
+ }
+
+
+ let retArr = [];
+ for (let k in keyList) {
+ retArr.push(keyList[k]);
+ }
+
+ if (interactive && retArr.length === 1) {
+ key = retArr[0];
+ if (("revoke" in key) && (!("name" in key))) {
+ this.importRevocationCert(key.id, blocks.join("\n"));
+ return [];
+ }
+ }
+
+ return retArr;
+ },
+
+ /**
+ * Get details of a key block to import. Works identically as getKeyListFromKeyBlock();
+ * except that the input is a file instead of a string
+ *
+ * @param file nsIFile object - file to read
+ * @param errorMsgObj Object - obj.value will contain error message
+ *
+ * @return Array (same as for getKeyListFromKeyBlock())
+ */
+ getKeyListFromKeyFile: function(path, errorMsgObj) {
+ var contents = EnigmailFiles.readFile(path);
+ return this.getKeyListFromKeyBlock(contents, errorMsgObj);
+ },
+
+
+ /**
+ * Compare 2 KeyIds of possible different length (short, long, FPR-length, with or without prefixed
+ * 0x are accepted)
+ *
+ * @param keyId1 string
+ * @param keyId2 string
+ *
+ * @return true or false, given the comparison of the last minimum-length characters.
+ */
+ compareKeyIds: function(keyId1, keyId2) {
+ var keyId1Raw = keyId1.replace(/^0x/, "").toUpperCase();
+ var keyId2Raw = keyId2.replace(/^0x/, "").toUpperCase();
+
+ var minlength = Math.min(keyId1Raw.length, keyId2Raw.length);
+
+ if (minlength < keyId1Raw.length) {
+ // Limit keyId1 to minlength
+ keyId1Raw = keyId1Raw.substr(-minlength, minlength);
+ }
+
+ if (minlength < keyId2Raw.length) {
+ // Limit keyId2 to minlength
+ keyId2Raw = keyId2Raw.substr(-minlength, minlength);
+ }
+
+ return (keyId1Raw === keyId2Raw);
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/keyEditor.jsm
@@ -0,0 +1,1392 @@
+
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailKeyEditor"];
+
+
+
+
+
+const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
+const EnigmailKey = ChromeUtils.import("chrome://openpgp/content/modules/key.jsm").EnigmailKey;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailOS = ChromeUtils.import("chrome://openpgp/content/modules/os.jsm").EnigmailOS;
+const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
+const EnigmailExecution = ChromeUtils.import("chrome://openpgp/content/modules/execution.jsm").EnigmailExecution;
+const EnigmailGpgAgent = ChromeUtils.import("chrome://openpgp/content/modules/gpgAgent.jsm").EnigmailGpgAgent;
+const EnigmailGpg = ChromeUtils.import("chrome://openpgp/content/modules/gpg.jsm").EnigmailGpg;
+const EnigmailKeyRing = ChromeUtils.import("chrome://openpgp/content/modules/keyRing.jsm").EnigmailKeyRing;
+const EnigmailErrorHandling = ChromeUtils.import("chrome://openpgp/content/modules/errorHandling.jsm").EnigmailErrorHandling;
+const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
+
+const GET_BOOL = "GET_BOOL";
+const GET_LINE = "GET_LINE";
+const GET_HIDDEN = "GET_HIDDEN";
+
+const NS_PROMPTSERVICE_CONTRACTID = "@mozilla.org/embedcomp/prompt-service;1";
+
+function GpgEditorInterface(reqObserver, callbackFunc, inputData) {
+ this._reqObserver = reqObserver;
+ this._callbackFunc = callbackFunc;
+ this._inputData = inputData;
+
+ if (this._inputData && this._inputData.cardAdmin) {
+ this._saveCmd = "quit";
+ }
+ else
+ this._saveCmd = "save";
+}
+
+
+GpgEditorInterface.prototype = {
+ _stdin: null,
+ _data: "",
+ _txt: "",
+ _exitCode: 0,
+ errorMsg: "",
+
+ setStdin: function(pipe) {
+ this._stdin = pipe;
+ if (this._data.length > 0) this.processData();
+ },
+
+ gotData: function(data) {
+ //EnigmailLog.DEBUG("keyEditor.jsm: GpgEditorInterface.gotData: '"+data+"'\n");
+ this._data += data.replace(/\r\n/g, "\n");
+ this.processData();
+ },
+
+ processData: function() {
+ //EnigmailLog.DEBUG("keyEditor.jsm: GpgEditorInterface.processData\n");
+ var txt = "";
+ while (this._data.length > 0 && this._stdin) {
+ var index = this._data.indexOf("\n");
+ if (index < 0) {
+ txt = this._data;
+ this._data = "";
+ }
+ else {
+ txt = this._data.substr(0, index);
+ this._data = this._data.substr(index + 1);
+ }
+ this.nextLine(txt);
+ }
+ },
+
+ closeStdin: function() {
+ EnigmailLog.DEBUG("keyEditor.jsm: GpgEditorInterface.closeStdin:\n");
+ if (this._stdin) {
+ this._stdin.close();
+ this._stdin = null;
+ }
+ },
+
+ onComplete: function(parentCallback, exitCode) {
+ EnigmailLog.DEBUG("keyEdit.jsm: GpgEditorInterface.onComplete: exitCode=" + exitCode + "\n");
+
+ if (exitCode === 0) exitCode = this._exitCode;
+
+ EnigmailLog.DEBUG("keyEdit.jsm: GpgEditorInterface.onComplete: returning exitCode " + exitCode + "\n");
+
+ parentCallback(exitCode, this.errorMsg);
+ },
+
+ writeLine: function(inputData) {
+ EnigmailLog.DEBUG("keyEdit.jsm: GpgEditorInterface.writeLine: '" + inputData + "'\n");
+ this._stdin.write(inputData + "\n");
+ },
+
+ nextLine: function(txt) {
+ if (txt.indexOf("[GNUPG:]") >= 0) {
+ if (this._reqObserver) {
+ var newTxt = this._reqObserver.onDataAvailable(txt);
+ if (newTxt.length > 0) {
+
+ txt = newTxt;
+ }
+ }
+ this._txt = txt;
+ this.processLine(txt);
+ }
+ },
+
+ doCheck: function(inputType, promptVal) {
+ var a = this._txt.split(/ /);
+ return ((a[1] == inputType) && (a[2] == promptVal));
+ },
+
+ getText: function() {
+ return this._txt;
+ },
+
+ handleGpgError: function(lineTxt) {
+ let retStatusObj = {};
+
+ EnigmailErrorHandling.parseErrorOutput(lineTxt, retStatusObj);
+ return retStatusObj;
+ },
+
+ processLine: function(txt) {
+ EnigmailLog.DEBUG("keyEdit.jsm: GpgEditorInterface.processLine: '" + txt + "'\n");
+ var r = {
+ quitNow: false,
+ exitCode: -1
+ };
+
+ try {
+ if (txt.indexOf("[GNUPG:] BAD_PASSPHRASE") >= 0 ||
+ txt.indexOf("[GNUPG:] SC_OP_FAILURE 2") >= 0) {
+ EnigmailLog.DEBUG("keyEdit.jsm: GpgEditorInterface.processLine: detected bad passphrase\n");
+ r.exitCode = -2;
+ r.quitNow = true;
+ this.errorMsg = EnigmailLocale.getString("badPhrase");
+ }
+ else if (txt.indexOf("[GNUPG:] ERROR ") >= 0 || txt.indexOf("[GNUPG:] FAILURE ") >= 0) {
+ EnigmailLog.DEBUG("keyEdit.jsm: GpgEditorInterface.processLine: detected GnuPG ERROR message\n");
+ let statusObj = this.handleGpgError(txt);
+ if (statusObj.statusFlags & EnigmailConstants.DISPLAY_MESSAGE) {
+ this.errorMsg = statusObj.statusMsg;
+ r.exitCode = -3;
+ r.quitNow = true;
+ }
+ }
+ else if (txt.indexOf("[GNUPG:] NO_CARD_AVAILABLE") >= 0) {
+ EnigmailLog.DEBUG("keyEdit.jsm: GpgEditorInterface.processLine: detected missing card\n");
+ this.errorMsg = EnigmailLocale.getString("sc.noCardAvailable");
+ r.exitCode = -3;
+ r.quitNow = true;
+ }
+ else if (txt.indexOf("[GNUPG:] ENIGMAIL_FAILURE") === 0) {
+ EnigmailLog.DEBUG("keyEdit.jsm: GpgEditorInterface.processLine: detected general failure\n");
+ r.exitCode = -3;
+ r.quitNow = true;
+ this.errorMsg = txt.substr(26);
+ }
+ else if (txt.indexOf("[GNUPG:] ALREADY_SIGNED") >= 0) {
+ EnigmailLog.DEBUG("keyEdit.jsm: GpgEditorInterface.processLine: detected key already signed\n");
+ this.errorMsg = EnigmailLocale.getString("keyAlreadySigned");
+ r.exitCode = -1;
+ r.quitNow = true;
+ }
+ else if (txt.indexOf("[GNUPG:] MISSING_PASSPHRASE") >= 0) {
+ EnigmailLog.DEBUG("keyEdit.jsm: GpgEditorInterface.processLine: detected missing passphrase\n");
+ this.errorMsg = EnigmailLocale.getString("noPassphrase");
+ r.exitCode = -2;
+ this._exitCode = -2;
+ r.quitNow = true;
+ }
+ else if (txt.indexOf("[GNUPG:] GET_") < 0) {
+ // return if no "GET" statement
+ return;
+ }
+ }
+ catch (ex) {
+ txt = "";
+ r.quitNow = true;
+ }
+
+ if (!r.quitNow) {
+ if (txt.indexOf("[GNUPG:] GOT_IT") < 0) {
+ if (this._callbackFunc) {
+ this._callbackFunc(this._inputData, this, r);
+ if (r.exitCode === 0) {
+ this.writeLine(r.writeTxt);
+ }
+ else {
+ if (r.errorMsg && r.errorMsg.length > 0)
+ this.errorMsg = r.errorMsg;
+ }
+ }
+ else {
+ r.quitNow = true;
+ r.exitCode = 0;
+ }
+ }
+ else {
+ r.exitCode = 0;
+ }
+ }
+
+ if (r.quitNow) {
+ try {
+ this.writeLine(this._saveCmd);
+ this.closeStdin();
+ }
+ catch (ex) {
+ EnigmailLog.DEBUG("no more data\n");
+ }
+ }
+
+ if (r.exitCode !== null)
+ this._exitCode = r.exitCode;
+ },
+
+ QueryInterface: function(iid) {
+ if (!iid.equals(Ci.nsISupports))
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ return this;
+ }
+};
+
+function editKey(parent, needPassphrase, userId, keyId, editCmd, inputData, callbackFunc, requestObserver, parentCallback) {
+ EnigmailLog.DEBUG("keyEdit.jsm: editKey: parent=" + parent + ", editCmd=" + editCmd + "\n");
+
+ if (!EnigmailCore.getService(parent)) {
+ EnigmailLog.ERROR("keyEdit.jsm: Enigmail.editKey: not yet initialized\n");
+ parentCallback(-1, EnigmailLocale.getString("notInit"));
+ return -1;
+ }
+
+ var keyIdList = keyId.split(" ");
+ var args = EnigmailGpg.getStandardArgs(false);
+
+ var statusFlags = {};
+
+ args = args.concat(["--no-tty", "--no-verbose", "--status-fd", "1", "--logger-fd", "1", "--command-fd", "0"]);
+ if (userId) args = args.concat(["-u", userId]);
+ var editCmdArr;
+ if (typeof(editCmd) == "string") {
+ editCmdArr = [editCmd];
+ }
+ else {
+ editCmdArr = editCmd;
+ }
+
+ if (editCmdArr[0] == "revoke") {
+ // escape backslashes and ' characters
+ args = args.concat(["-a", "-o"]);
+ args.push(EnigmailFiles.getEscapedFilename(inputData.outFile.path));
+ args.push("--gen-revoke");
+ args = args.concat(keyIdList);
+ }
+ else if (editCmdArr[0].indexOf("--") === 0) {
+ args = args.concat(editCmd);
+ args = args.concat(keyIdList);
+ }
+ else {
+ args = args.concat(["--ask-cert-level", "--edit-key", keyId]);
+ args = args.concat(editCmd);
+ }
+
+
+ var command = EnigmailGpgAgent.agentPath;
+ EnigmailLog.CONSOLE("enigmail> " + EnigmailFiles.formatCmdLine(command, args) + "\n");
+
+ var keyEdit = new GpgEditorInterface(requestObserver, callbackFunc, inputData);
+
+ try {
+ EnigmailExecution.execCmd2(command, args,
+ keyEdit.setStdin.bind(keyEdit),
+ keyEdit.gotData.bind(keyEdit),
+ function(result) {
+ EnigmailKeyRing.updateKeys(keyIdList);
+ keyEdit.onComplete(parentCallback, 0); // ignore exit code from GnuPG
+ }
+ );
+ }
+ catch (ex) {
+ EnigmailLog.ERROR("keyEditor.jsm: editKey: " + command.path + " failed\n");
+ parentCallback(-1, "");
+ }
+
+ return null;
+}
+
+function runKeyTrustCheck(callbackFunc) {
+ EnigmailLog.DEBUG("keyEdit.jsm: runKeyTrustCheck()\n");
+
+ let args = EnigmailGpg.getStandardArgs(true);
+ args = args.concat(["--yes", "--check-trustdb"]);
+
+ EnigmailExecution.execCmd2(EnigmailGpgAgent.agentPath,
+ args,
+ null,
+ function stdout(data) {
+ EnigmailLog.DEBUG(data);
+ },
+ function(result) {
+ EnigmailLog.DEBUG("keyEdit.jsm: runKeyTrustCheck: done\n");
+ });
+}
+
+/*
+ * NOTE: the callbackFunc used in every call to the key editor needs to be implemented like this:
+ * callbackFunc(returnCode, errorMsg)
+ * returnCode = 0 in case of success
+ * returnCode != 0 and errorMsg set in case of failure
+ */
+var EnigmailKeyEditor = {
+ setKeyTrust: function(parent, keyId, trustLevel, callbackFunc) {
+ EnigmailLog.DEBUG("keyEdit.jsm: Enigmail.setKeyTrust: trustLevel=" + trustLevel + ", keyId=" + keyId + "\n");
+
+ return editKey(parent, false, null, keyId, "trust", {
+ trustLevel: trustLevel
+ },
+ keyTrustCallback,
+ null,
+ function _f(returnCode, errorMsg) {
+ runKeyTrustCheck();
+ EnigmailKeyRing.updateKeys([keyId]);
+ callbackFunc(returnCode, errorMsg);
+ });
+ },
+
+
+ /**
+ * Call editKey() to set the expiration date of the chosen key and subkeys
+ *
+ * @param Object parent
+ * @param String keyId e.g. 8D18EB22FDF633A2
+ * @param Array subKeys List of Integer values, e.g. [0,1,3]
+ * "0" should allways be set because it's the main key.
+ * @param Integer expiryLength A number between 1 and 100
+ * @param Integer timeScale 1 or 30 or 365 meaning days, months, years
+ * @param Boolean noExpiry True: Expire never. False: Use expiryLength.
+ * @param Function callbackFunc will be executed by editKey()
+ * @return Integer
+ * returnCode = 0 in case of success
+ * returnCode != 0 and errorMsg set in case of failure
+ */
+ setKeyExpiration: function(parent, keyId, subKeys, expiryLength, timeScale, noExpiry, callbackFunc) {
+ EnigmailLog.DEBUG("keyEdit.jsm: Enigmail.setKeyExpiry: keyId=" + keyId + "\n");
+
+ expiryLength = String(expiryLength);
+ if (noExpiry === true) {
+ expiryLength = "0";
+ }
+ else {
+ switch (parseInt(timeScale, 10)) {
+ case 365:
+ expiryLength += "y";
+ break;
+ case 30:
+ expiryLength += "m";
+ break;
+ case 7:
+ expiryLength += "w";
+ break;
+ }
+ }
+
+ return editKey(parent,
+ true,
+ null,
+ keyId,
+ "", /* "expire", */ {
+ expiryLength: expiryLength,
+ subKeys: subKeys,
+ currentSubKey: false
+ },
+ keyExpiryCallback, /* contains the gpg communication logic */
+ null,
+ callbackFunc);
+ },
+
+
+ signKey: function(parent, userId, keyId, signLocally, trustLevel, callbackFunc) {
+ EnigmailLog.DEBUG("keyEdit.jsm: Enigmail.signKey: trustLevel=" + trustLevel + ", userId=" + userId + ", keyId=" + keyId + "\n");
+ return editKey(parent, true, userId, keyId, (signLocally ? "lsign" : "sign"), {
+ trustLevel: trustLevel,
+ usePassphrase: true
+ },
+ signKeyCallback,
+ null,
+ function _f(returnCode, errorMsg) {
+ runKeyTrustCheck();
+ EnigmailKeyRing.updateKeys([keyId]);
+ callbackFunc(returnCode, errorMsg);
+ });
+
+ },
+
+ genRevokeCert: function(parent, keyId, outFile, reasonCode, reasonText, callbackFunc) {
+ EnigmailLog.DEBUG("keyEdit.jsm: Enigmail.genRevokeCert: keyId=" + keyId + "\n");
+
+ /**
+ * GnuPG < 2.1 does not properly report failures;
+ * therefore we check if the revokation certificate was really generated
+ */
+ function checkGeneratedCert(exitCode, errorMsg) {
+ if (!outFile.exists()) {
+ exitCode = 1;
+ errorMsg = "";
+ }
+ callbackFunc(exitCode, errorMsg);
+ }
+
+ return editKey(parent, true, null, keyId, "revoke", {
+ outFile: outFile,
+ reasonCode: reasonCode,
+ reasonText: EnigmailData.convertFromUnicode(reasonText),
+ usePassphrase: true
+ },
+ revokeCertCallback,
+ null,
+ checkGeneratedCert);
+ },
+
+ addUid: function(parent, keyId, name, email, comment, callbackFunc) {
+ EnigmailLog.DEBUG("keyEdit.jsm: Enigmail.addUid: keyId=" + keyId + ", name=" + name + ", email=" + email + "\n");
+ return editKey(parent, true, null, keyId, "adduid", {
+ email: email,
+ name: name,
+ comment: comment,
+ nameAsked: 0,
+ emailAsked: 0,
+ usePassphrase: true
+ },
+ addUidCallback,
+ null,
+ callbackFunc);
+ },
+
+ deleteKey: function(parent, keyId, deleteSecretKey, callbackFunc) {
+ EnigmailLog.DEBUG("keyEdit.jsm: Enigmail.addUid: keyId=" + keyId + ", deleteSecretKey=" + deleteSecretKey + "\n");
+
+ var cmd = ["--yes", (deleteSecretKey ? "--delete-secret-and-public-key" : "--delete-key")];
+ return editKey(parent, false, null, keyId, cmd, {
+ usePassphrase: true
+ },
+ deleteKeyCallback,
+ null,
+ callbackFunc);
+ },
+
+ changePassphrase: function(parent, keyId, oldPw, newPw, callbackFunc) {
+ EnigmailLog.DEBUG("keyEdit.jsm: Enigmail.changePassphrase: keyId=" + keyId + "\n");
+
+ var pwdObserver = new ChangePasswdObserver();
+ return editKey(parent, false, null, keyId, "passwd", {
+ oldPw: oldPw,
+ newPw: newPw,
+ step: 0,
+ observer: pwdObserver,
+ usePassphrase: true
+ },
+ changePassphraseCallback,
+ pwdObserver,
+ callbackFunc);
+ },
+
+
+ enableDisableKey: function(parent, keyId, disableKey, callbackFunc) {
+ EnigmailLog.DEBUG("keyEdit.jsm: Enigmail.enableDisableKey: keyId=" + keyId + ", disableKey=" + disableKey + "\n");
+
+ var cmd = (disableKey ? "disable" : "enable");
+ return editKey(parent, false, null, keyId, cmd, {
+ usePassphrase: true
+ },
+ null,
+ null,
+ callbackFunc);
+ },
+
+ setPrimaryUid: function(parent, keyId, idNumber, callbackFunc) {
+ EnigmailLog.DEBUG("keyEdit.jsm: Enigmail.setPrimaryUid: keyId=" + keyId + ", idNumber=" + idNumber + "\n");
+ return editKey(parent, true, null, keyId, "", {
+ idNumber: idNumber,
+ step: 0,
+ usePassphrase: true
+ },
+ setPrimaryUidCallback,
+ null,
+ callbackFunc);
+ },
+
+
+ deleteUid: function(parent, keyId, idNumber, callbackFunc) {
+ EnigmailLog.DEBUG("keyEdit.jsm: Enigmail.deleteUid: keyId=" + keyId + ", idNumber=" + idNumber + "\n");
+ return editKey(parent, true, null, keyId, "", {
+ idNumber: idNumber,
+ step: 0,
+ usePassphrase: true
+ },
+ deleteUidCallback,
+ null,
+ callbackFunc);
+ },
+
+
+ revokeUid: function(parent, keyId, idNumber, callbackFunc) {
+ EnigmailLog.DEBUG("keyEdit.jsm: Enigmail.revokeUid: keyId=" + keyId + ", idNumber=" + idNumber + "\n");
+ return editKey(parent, true, null, keyId, "", {
+ idNumber: idNumber,
+ step: 0,
+ usePassphrase: true
+ },
+ revokeUidCallback,
+ null,
+ callbackFunc);
+ },
+
+ addPhoto: function(parent, keyId, photoFile, callbackFunc) {
+ EnigmailLog.DEBUG("keyEdit.jsm: Enigmail.addPhoto: keyId=" + keyId + "\n");
+
+ var photoFileName = EnigmailFiles.getEscapedFilename(EnigmailFiles.getFilePath(photoFile.QueryInterface(Ci.nsIFile)));
+
+ return editKey(parent, true, null, keyId, "addphoto", {
+ file: photoFileName,
+ step: 0,
+ usePassphrase: true
+ },
+ addPhotoCallback,
+ null,
+ function _f(returnCode, errorMsg) {
+ runKeyTrustCheck();
+ EnigmailKeyRing.updateKeys([keyId]);
+ callbackFunc(returnCode, errorMsg);
+ });
+ },
+
+
+ genCardKey: function(parent, name, email, comment, expiry, backupPasswd, requestObserver, callbackFunc) {
+ EnigmailLog.DEBUG("keyEdit.jsm: Enigmail.genCardKey: \n");
+ var generateObserver = new EnigCardAdminObserver(requestObserver, EnigmailOS.isDosLike);
+ return editKey(parent, false, null, "", ["--with-colons", "--card-edit"], {
+ step: 0,
+ name: EnigmailData.convertFromUnicode(name),
+ email: email,
+ comment: EnigmailData.convertFromUnicode(comment),
+ expiry: expiry,
+ backupPasswd: backupPasswd,
+ cardAdmin: true,
+ backupKey: (backupPasswd.length > 0 ? "Y" : "N"),
+ parent: parent
+ },
+ genCardKeyCallback,
+ generateObserver,
+ callbackFunc);
+ },
+
+ cardAdminData: function(parent, name, firstname, lang, sex, url, login, forcepin, callbackFunc) {
+ EnigmailLog.DEBUG("keyEdit.jsm: Enigmail.cardAdminData: parent=" + parent + ", name=" + name + ", firstname=" + firstname + ", lang=" + lang + ", sex=" + sex + ", url=" + url +
+ ", login=" + login + ", forcepin=" + forcepin + "\n");
+ var adminObserver = new EnigCardAdminObserver(null, EnigmailOS.isDosLike);
+ return editKey(parent, false, null, "", ["--with-colons", "--card-edit"], {
+ step: 0,
+ name: name,
+ firstname: firstname,
+ lang: lang,
+ sex: sex,
+ url: url,
+ login: login,
+ cardAdmin: true,
+ forcepin: forcepin
+ },
+ cardAdminDataCallback,
+ adminObserver,
+ callbackFunc);
+ },
+
+ cardChangePin: function(parent, action, oldPin, newPin, adminPin, pinObserver, callbackFunc) {
+ EnigmailLog.DEBUG("keyEdit.jsm: Enigmail.cardChangePin: parent=" + parent + ", action=" + action + "\n");
+ var adminObserver = new EnigCardAdminObserver(pinObserver, EnigmailOS.isDosLike);
+
+ return editKey(parent, true, null, "", ["--with-colons", "--card-edit"], {
+ step: 0,
+ pinStep: 0,
+ cardAdmin: true,
+ action: action,
+ oldPin: oldPin,
+ newPin: newPin,
+ adminPin: adminPin
+ },
+ cardChangePinCallback,
+ adminObserver,
+ callbackFunc);
+ }
+
+}; // EnigmailKeyEditor
+
+
+function signKeyCallback(inputData, keyEdit, ret) {
+
+ ret.writeTxt = "";
+ ret.errorMsg = "";
+
+ if (keyEdit.doCheck(GET_BOOL, "sign_uid.okay")) {
+ ret.exitCode = 0;
+ ret.writeTxt = "Y";
+ }
+ else if (keyEdit.doCheck(GET_BOOL, "keyedit.sign_all.okay")) {
+ ret.exitCode = 0;
+ ret.writeTxt = "Y";
+ }
+ else if (keyEdit.doCheck(GET_LINE, "sign_uid.expire")) {
+ ret.exitCode = 0;
+ ret.writeTxt = "0";
+ }
+ else if (keyEdit.doCheck(GET_LINE, "trustsig_prompt.trust_value")) {
+ ret.exitCode = 0;
+ ret.writeTxt = "0";
+ }
+ else if (keyEdit.doCheck(GET_LINE, "trustsig_prompt.trust_depth")) {
+ ret.exitCode = 0;
+ ret.writeTxt = "";
+ }
+ else if (keyEdit.doCheck(GET_LINE, "trustsig_prompt.trust_regexp")) {
+ ret.exitCode = 0;
+ ret.writeTxt = "0";
+ }
+ else if (keyEdit.doCheck(GET_LINE, "siggen.valid")) {
+ ret.exitCode = 0;
+ ret.writeTxt = "0";
+ }
+ else if (keyEdit.doCheck(GET_BOOL, "sign_uid.local_promote_okay")) {
+ ret.exitCode = 0;
+ ret.writeTxt = "Y";
+ }
+ else if (keyEdit.doCheck(GET_BOOL, "sign_uid.replace_expired_okay")) {
+ ret.exitCode = 0;
+ ret.writeTxt = "Y";
+ }
+ else if (keyEdit.doCheck(GET_LINE, "sign_uid.class")) {
+ ret.exitCode = 0;
+ ret.writeTxt = String(inputData.trustLevel);
+ }
+ else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.adminpin.ask")) {
+ getPin(inputData.parent, EnigmailLocale.getString("enterAdminPin"), ret);
+ }
+ else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.pin.ask")) {
+ getPin(inputData.parent, EnigmailLocale.getString("enterCardPin"), ret);
+ }
+ else if (keyEdit.doCheck(GET_LINE, "keyedit.prompt")) {
+ ret.exitCode = 0;
+ ret.quitNow = true;
+ }
+ else {
+ ret.quitNow = true;
+ EnigmailLog.ERROR("Unknown command prompt: " + keyEdit.getText() + "\n");
+ ret.exitCode = -1;
+ }
+}
+
+function keyTrustCallback(inputData, keyEdit, ret) {
+ ret.writeTxt = "";
+ ret.errorMsg = "";
+
+ if (keyEdit.doCheck(GET_LINE, "edit_ownertrust.value")) {
+ ret.exitCode = 0;
+ ret.writeTxt = String(inputData.trustLevel);
+ }
+ else if (keyEdit.doCheck(GET_BOOL, "edit_ownertrust.set_ultimate.okay")) {
+ ret.exitCode = 0;
+ ret.writeTxt = "Y";
+ }
+ else if (keyEdit.doCheck(GET_LINE, "keyedit.prompt")) {
+ ret.exitCode = 0;
+ ret.quitNow = true;
+ }
+ else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.adminpin.ask")) {
+ getPin(inputData.parent, EnigmailLocale.getString("enterAdminPin"), ret);
+ }
+ else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.pin.ask")) {
+ getPin(inputData.parent, EnigmailLocale.getString("enterCardPin"), ret);
+ }
+ else {
+ ret.quitNow = true;
+ EnigmailLog.ERROR("Unknown command prompt: " + keyEdit.getText() + "\n");
+ ret.exitCode = -1;
+ }
+}
+
+/**
+ *
+ * @param Array inputData Has the keys ...
+ * expiryLength (String): e.g. 8m = 8 month, 5 = 5 days, 3y = 3 years, 0 = never
+ * subKeys (array): list of still unprocessed subkeys
+ * currentSubKey (Integer or false): current subkey in progress
+ * @param Object keyEdit Readonly messages from GPG.
+ * @param Object ret
+ */
+function keyExpiryCallback(inputData, keyEdit, ret) {
+ EnigmailLog.DEBUG("keyEdit.jsm: keyExpiryCallback()\n");
+
+ ret.writeTxt = "";
+ ret.errorMsg = "";
+
+ if (inputData.subKeys.length === 0) {
+ // zero keys are submitted to edit: this must be a mistake.
+ ret.exitCode = -1;
+ ret.quitNow = true;
+ }
+ else if (keyEdit.doCheck(GET_LINE, "keyedit.prompt")) {
+ if (inputData.currentSubKey === false) {
+ // currently no subkey is selected. Chose the first subkey.
+ inputData.currentSubKey = inputData.subKeys[0];
+ ret.exitCode = 0;
+ ret.writeTxt = "key " + inputData.currentSubKey;
+ }
+ else if (inputData.currentSubKey === inputData.subKeys[0]) {
+ // a subkey is selected. execute command "expire"
+ ret.exitCode = 0;
+ ret.writeTxt = "expire";
+ }
+ else {
+ // if (inputData.currentSubKey === inputData.subKeys[0])
+ // unselect the previous used subkey
+ ret.exitCode = 0;
+ ret.writeTxt = "key " + inputData.currentSubKey;
+ inputData.currentSubKey = false;
+ }
+ }
+ else if (keyEdit.doCheck(GET_LINE, "keygen.valid")) {
+ // submit the expiry length.
+ ret.exitCode = 0;
+ ret.writeTxt = inputData.expiryLength;
+ // processing of the current subkey is through.
+ // remove current subkey from list of "to be processed keys".
+ inputData.subKeys.splice(0, 1);
+ // if the list of "to be processed keys" is empty, then quit.
+ if (inputData.subKeys.length === 0) {
+ ret.quitNow = true;
+ }
+ }
+ else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.adminpin.ask")) {
+ getPin(inputData.parent, EnigmailLocale.getString("enterAdminPin"), ret);
+ }
+ else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.pin.ask")) {
+ getPin(inputData.parent, EnigmailLocale.getString("enterCardPin"), ret);
+ }
+ else {
+ ret.quitNow = true;
+ EnigmailLog.ERROR("Unknown command prompt: " + keyEdit.getText() + "\n");
+ ret.exitCode = -1;
+ }
+}
+
+function addUidCallback(inputData, keyEdit, ret) {
+ ret.writeTxt = "";
+ ret.errorMsg = "";
+
+ if (keyEdit.doCheck(GET_LINE, "keygen.name")) {
+ ++inputData.nameAsked;
+ if (inputData.nameAsked == 1) {
+ ret.exitCode = 0;
+ ret.writeTxt = inputData.name;
+ }
+ else {
+ ret.exitCode = -1;
+ ret.quitNow = true;
+ ret.errorMsg = "Invalid name (too short)";
+ }
+ }
+ else if (keyEdit.doCheck(GET_LINE, "keygen.email")) {
+ ++inputData.emailAsked;
+ if (inputData.emailAsked == 1) {
+ ret.exitCode = 0;
+ ret.writeTxt = inputData.email;
+ }
+ else {
+ ret.exitCode = -1;
+ ret.quitNow = true;
+ ret.errorMsg = "Invalid email";
+ }
+ }
+ else if (keyEdit.doCheck(GET_LINE, "keygen.comment")) {
+ ret.exitCode = 0;
+ if (inputData.comment) {
+ ret.writeTxt = inputData.comment;
+ }
+ else {
+ ret.writeTxt = "";
+ }
+ }
+ else if (keyEdit.doCheck(GET_LINE, "keyedit.prompt")) {
+ ret.exitCode = 0;
+ ret.quitNow = true;
+ }
+ else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.adminpin.ask")) {
+ getPin(inputData.parent, EnigmailLocale.getString("enterAdminPin"), ret);
+ }
+ else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.pin.ask")) {
+ getPin(inputData.parent, EnigmailLocale.getString("enterCardPin"), ret);
+ }
+ else {
+ ret.quitNow = true;
+ EnigmailLog.ERROR("Unknown command prompt: " + keyEdit.getText() + "\n");
+ ret.exitCode = -1;
+ }
+}
+
+
+function revokeCertCallback(inputData, keyEdit, ret) {
+ ret.writeTxt = "";
+ ret.errorMsg = "";
+
+ if (keyEdit.doCheck(GET_LINE, "ask_revocation_reason.code")) {
+ ret.exitCode = 0;
+ ret.writeTxt = String(inputData.reasonCode);
+ }
+ else if (keyEdit.doCheck(GET_LINE, "ask_revocation_reason.text")) {
+ ret.exitCode = 0;
+ ret.writeTxt = "";
+ }
+ else if (keyEdit.doCheck(GET_BOOL, "gen_revoke.okay")) {
+ ret.exitCode = 0;
+ ret.writeTxt = "Y";
+ }
+ else if (keyEdit.doCheck(GET_BOOL, "ask_revocation_reason.okay")) {
+ ret.exitCode = 0;
+ ret.writeTxt = "Y";
+ }
+ else if (keyEdit.doCheck(GET_BOOL, "openfile.overwrite.okay")) {
+ ret.exitCode = 0;
+ ret.writeTxt = "Y";
+ }
+ else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.adminpin.ask")) {
+ getPin(inputData.parent, EnigmailLocale.getString("enterAdminPin"), ret);
+ }
+ else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.pin.ask")) {
+ getPin(inputData.parent, EnigmailLocale.getString("enterCardPin"), ret);
+ }
+ else if (keyEdit.doCheck(GET_LINE, "keyedit.prompt")) {
+ ret.exitCode = 0;
+ ret.quitNow = true;
+ }
+ else {
+ ret.quitNow = true;
+ EnigmailLog.ERROR("Unknown command prompt: " + keyEdit.getText() + "\n");
+ ret.exitCode = -1;
+ }
+}
+
+
+function setPrimaryUidCallback(inputData, keyEdit, ret) {
+ ret.writeTxt = "";
+ ret.errorMsg = "";
+
+ if (keyEdit.doCheck(GET_LINE, "keyedit.prompt")) {
+ ++inputData.step;
+ switch (inputData.step) {
+ case 1:
+ ret.exitCode = 0;
+ ret.writeTxt = "uid " + inputData.idNumber;
+ break;
+ case 2:
+ ret.exitCode = 0;
+ ret.writeTxt = "primary";
+ break;
+ case 3:
+ ret.exitCode = 0;
+ ret.quitNow = true;
+ break;
+ default:
+ ret.exitCode = -1;
+ ret.quitNow = true;
+ }
+
+ }
+ else {
+ ret.quitNow = true;
+ EnigmailLog.ERROR("Unknown command prompt: " + keyEdit.getText() + "\n");
+ ret.exitCode = -1;
+ }
+}
+
+
+function changePassphraseCallback(inputData, keyEdit, ret) {
+ ret.writeTxt = "";
+ ret.errorMsg = "";
+
+ if (keyEdit.doCheck(GET_HIDDEN, "passphrase.enter")) {
+ switch (inputData.observer.passphraseStatus) {
+ case 0:
+ ret.writeTxt = inputData.oldPw;
+ ret.exitCode = 0;
+ break;
+ case 1:
+ ret.writeTxt = inputData.newPw;
+ ret.exitCode = 0;
+ break;
+ case -1:
+ ret.exitCode = -2;
+ ret.quitNow = true;
+ break;
+ }
+ }
+ else if (keyEdit.doCheck(GET_BOOL, "change_passwd.empty.okay")) {
+ ret.writeTxt = "Y";
+ ret.exitCode = 0;
+ }
+ else if (keyEdit.doCheck(GET_LINE, "keyedit.prompt")) {
+ ret.exitCode = 0;
+ ret.quitNow = true;
+ }
+ else {
+ ret.quitNow = true;
+ EnigmailLog.ERROR("Unknown command prompt: " + keyEdit.getText() + "\n");
+ ret.exitCode = -1;
+ }
+}
+
+
+function deleteUidCallback(inputData, keyEdit, ret) {
+ ret.writeTxt = "";
+ ret.errorMsg = "";
+
+ if (keyEdit.doCheck(GET_LINE, "keyedit.prompt")) {
+ ++inputData.step;
+ switch (inputData.step) {
+ case 1:
+ ret.exitCode = 0;
+ ret.writeTxt = "uid " + inputData.idNumber;
+ break;
+ case 2:
+ ret.exitCode = 0;
+ ret.writeTxt = "deluid";
+ break;
+ case 4:
+ ret.exitCode = 0;
+ ret.quitNow = true;
+ break;
+ default:
+ ret.exitCode = -1;
+ ret.quitNow = true;
+ }
+ }
+ else if (keyEdit.doCheck(GET_BOOL, "keyedit.remove.uid.okay")) {
+ ++inputData.step;
+ ret.exitCode = 0;
+ ret.writeTxt = "Y";
+ }
+ else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.adminpin.ask")) {
+ getPin(inputData.parent, EnigmailLocale.getString("enterAdminPin"), ret);
+ }
+ else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.pin.ask")) {
+ getPin(inputData.parent, EnigmailLocale.getString("enterCardPin"), ret);
+ }
+ else {
+ ret.quitNow = true;
+ EnigmailLog.ERROR("Unknown command prompt: " + keyEdit.getText() + "\n");
+ ret.exitCode = -1;
+ }
+}
+
+
+function revokeUidCallback(inputData, keyEdit, ret) {
+ ret.writeTxt = "";
+ ret.errorMsg = "";
+
+ if (keyEdit.doCheck(GET_LINE, "keyedit.prompt")) {
+ ++inputData.step;
+ switch (inputData.step) {
+ case 1:
+ ret.exitCode = 0;
+ ret.writeTxt = "uid " + inputData.idNumber;
+ break;
+ case 2:
+ ret.exitCode = 0;
+ ret.writeTxt = "revuid";
+ break;
+ case 7:
+ ret.exitCode = 0;
+ ret.quitNow = true;
+ break;
+ default:
+ ret.exitCode = -1;
+ ret.quitNow = true;
+ }
+ }
+ else if (keyEdit.doCheck(GET_BOOL, "keyedit.revoke.uid.okay")) {
+ ++inputData.step;
+ ret.exitCode = 0;
+ ret.writeTxt = "Y";
+ }
+ else if (keyEdit.doCheck(GET_LINE, "ask_revocation_reason.code")) {
+ ++inputData.step;
+ ret.exitCode = 0;
+ ret.writeTxt = "0"; // no reason specified
+ }
+ else if (keyEdit.doCheck(GET_LINE, "ask_revocation_reason.text")) {
+ ++inputData.step;
+ ret.exitCode = 0;
+ ret.writeTxt = "";
+ }
+ else if (keyEdit.doCheck(GET_BOOL, "ask_revocation_reason.okay")) {
+ ++inputData.step;
+ ret.exitCode = 0;
+ ret.writeTxt = "Y";
+ }
+ else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.adminpin.ask")) {
+ getPin(inputData.parent, EnigmailLocale.getString("enterAdminPin"), ret);
+ }
+ else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.pin.ask")) {
+ getPin(inputData.parent, EnigmailLocale.getString("enterCardPin"), ret);
+ }
+ else {
+ ret.quitNow = true;
+ EnigmailLog.ERROR("Unknown command prompt: " + keyEdit.getText() + "\n");
+ ret.exitCode = -1;
+ }
+}
+
+
+function deleteKeyCallback(inputData, keyEdit, ret) {
+ ret.writeTxt = "";
+ ret.errorMsg = "";
+
+ if (keyEdit.doCheck(GET_BOOL, "delete_key.secret.okay")) {
+ ret.exitCode = 0;
+ ret.writeTxt = "Y";
+ }
+ else if (keyEdit.doCheck(GET_BOOL, "keyedit.remove.subkey.okay")) {
+ ret.exitCode = 0;
+ ret.writeTxt = "Y";
+ }
+ else if (keyEdit.doCheck(GET_BOOL, "delete_key.okay")) {
+ ret.exitCode = 0;
+ ret.writeTxt = "Y";
+ }
+ else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.adminpin.ask")) {
+ getPin(inputData.parent, EnigmailLocale.getString("enterAdminPin"), ret);
+ }
+ else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.pin.ask")) {
+ getPin(inputData.parent, EnigmailLocale.getString("enterCardPin"), ret);
+ }
+ else {
+ ret.quitNow = true;
+ EnigmailLog.ERROR("Unknown command prompt: " + keyEdit.getText() + "\n");
+ ret.exitCode = -1;
+ }
+}
+
+function getPin(domWindow, promptMsg, ret) {
+ EnigmailLog.DEBUG("keyEdit.jsm: getPin: \n");
+
+ var passwdObj = {
+ value: ""
+ };
+ var dummyObj = {};
+
+ var success = false;
+
+ var promptService = Cc[NS_PROMPTSERVICE_CONTRACTID].getService(Ci.nsIPromptService);
+ success = promptService.promptPassword(domWindow,
+ EnigmailLocale.getString("Enigmail"),
+ promptMsg,
+ passwdObj,
+ null,
+ dummyObj);
+
+ if (!success) {
+ ret.errorMsg = EnigmailLocale.getString("noPassphrase");
+ ret.quitNow = true;
+ return false;
+ }
+
+ EnigmailLog.DEBUG("keyEdit.jsm: getPin: got pin\n");
+ ret.writeTxt = passwdObj.value;
+
+ return true;
+}
+
+function genCardKeyCallback(inputData, keyEdit, ret) {
+ ret.writeTxt = "";
+ ret.errorMsg = "";
+
+ var pinObj = {};
+
+ if (keyEdit.doCheck(GET_LINE, "cardedit.prompt")) {
+ if (inputData.step === 0) {
+ ret.exitCode = 0;
+ ret.writeTxt = "admin";
+ }
+ else if (inputData.step == 1) {
+ ret.exitCode = 0;
+ ret.writeTxt = "generate";
+ }
+ else {
+ ret.exitCode = 0;
+ ret.quitNow = true;
+ ret.writeTxt = "quit";
+ }
+ ++inputData.step;
+ }
+ else if (keyEdit.doCheck(GET_LINE, "cardedit.genkeys.backup_enc") ||
+ keyEdit.doCheck(GET_BOOL, "cardedit.genkeys.backup_enc")) {
+ ret.exitCode = 0;
+ ret.writeTxt = String(inputData.backupKey);
+ }
+ else if (keyEdit.doCheck(GET_BOOL, "cardedit.genkeys.replace_keys")) {
+ ret.exitCode = 0;
+ ret.writeTxt = "Y";
+ }
+ else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.adminpin.ask")) {
+ getPin(inputData.parent, EnigmailLocale.getString("enterAdminPin"), ret);
+ }
+ else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.pin.ask")) {
+ getPin(inputData.parent, EnigmailLocale.getString("enterCardPin"), ret);
+ }
+ else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.enter")) {
+ ret.exitCode = 0;
+ ret.writeTxt = inputData.backupPasswd;
+ }
+ else if (keyEdit.doCheck(GET_LINE, "keygen.valid")) {
+ ret.exitCode = 0;
+ ret.writeTxt = String(inputData.expiry);
+ }
+ else if (keyEdit.doCheck(GET_LINE, "cardedit.genkeys.size")) {
+ ret.exitCode = 0;
+ ret.writeTxt = "2048";
+ }
+ else if (keyEdit.doCheck(GET_LINE, "keygen.name")) {
+ ret.exitCode = 0;
+ ret.writeTxt = inputData.name;
+ }
+ else if (keyEdit.doCheck(GET_LINE, "keygen.email")) {
+ ret.exitCode = 0;
+ ret.writeTxt = inputData.email;
+ }
+ else if (keyEdit.doCheck(GET_LINE, "keygen.comment")) {
+ ret.exitCode = 0;
+ if (inputData.comment) {
+ ret.writeTxt = inputData.comment;
+ }
+ else {
+ ret.writeTxt = "";
+ }
+ }
+ else {
+ ret.quitNow = true;
+ EnigmailLog.ERROR("Unknown command prompt: " + keyEdit.getText() + "\n");
+ ret.exitCode = -1;
+ }
+}
+
+function cardAdminDataCallback(inputData, keyEdit, ret) {
+ ret.writeTxt = "";
+ ret.errorMsg = "";
+
+ var pinObj = {};
+
+ if (keyEdit.doCheck(GET_LINE, "cardedit.prompt")) {
+ ++inputData.step;
+ ret.exitCode = 0;
+ switch (inputData.step) {
+ case 1:
+ ret.writeTxt = "admin";
+ break;
+ case 2:
+ ret.writeTxt = "name";
+ break;
+ case 3:
+ ret.writeTxt = "lang";
+ break;
+ case 4:
+ ret.writeTxt = "sex";
+ break;
+ case 5:
+ ret.writeTxt = "url";
+ break;
+ case 6:
+ ret.writeTxt = "login";
+ break;
+ case 7:
+ if (inputData.forcepin !== 0) {
+ ret.writeTxt = "forcesig";
+ }
+ else {
+ ret.writeTxt = "quit";
+ ret.exitCode = 0;
+ ret.quitNow = true;
+ }
+ break;
+ default:
+ ret.writeTxt = "quit";
+ ret.exitCode = 0;
+ ret.quitNow = true;
+ break;
+ }
+ }
+ else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.adminpin.ask")) {
+ getPin(inputData.parent, EnigmailLocale.getString("enterAdminPin"), ret);
+ }
+ else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.pin.ask")) {
+ getPin(inputData.parent, EnigmailLocale.getString("enterCardPin"), ret);
+ }
+ else if (keyEdit.doCheck(GET_LINE, "keygen.smartcard.surname")) {
+ ret.exitCode = 0;
+ ret.writeTxt = inputData.name.replace(/^$/, "-");
+ }
+ else if (keyEdit.doCheck(GET_LINE, "keygen.smartcard.givenname")) {
+ ret.exitCode = 0;
+ ret.writeTxt = inputData.firstname.replace(/^$/, "-");
+ }
+ else if (keyEdit.doCheck(GET_LINE, "cardedit.change_sex")) {
+ ret.exitCode = 0;
+ ret.writeTxt = inputData.sex;
+ }
+ else if (keyEdit.doCheck(GET_LINE, "cardedit.change_lang")) {
+ ret.exitCode = 0;
+ ret.writeTxt = inputData.lang.replace(/^$/, "-");
+ }
+ else if (keyEdit.doCheck(GET_LINE, "cardedit.change_url")) {
+ ret.exitCode = 0;
+ ret.writeTxt = inputData.url.replace(/^$/, "-");
+ }
+ else if (keyEdit.doCheck(GET_LINE, "cardedit.change_login")) {
+ ret.exitCode = 0;
+ ret.writeTxt = inputData.login.replace(/^$/, "-");
+ }
+ else {
+ ret.quitNow = true;
+ EnigmailLog.ERROR("Unknown command prompt: " + keyEdit.getText() + "\n");
+ ret.exitCode = -1;
+ }
+}
+
+function cardChangePinCallback(inputData, keyEdit, ret) {
+ ret.writeTxt = "";
+ ret.errorMsg = "";
+
+ if (keyEdit.doCheck(GET_LINE, "cardedit.prompt")) {
+ ++inputData.step;
+ ret.exitCode = 0;
+ switch (inputData.step) {
+ case 1:
+ ret.writeTxt = "admin";
+ break;
+ case 2:
+ ret.writeTxt = "passwd";
+ break;
+ default:
+ ret.writeTxt = "quit";
+ ret.exitCode = 0;
+ ret.quitNow = true;
+ break;
+ }
+ }
+ else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.adminpin.ask")) {
+ ret.exitCode = 0;
+ ret.writeTxt = inputData.adminPin;
+ }
+ else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.pin.ask")) {
+ ret.exitCode = 0;
+ ret.writeTxt = inputData.oldPin;
+ }
+ else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.pin.new.ask") ||
+ keyEdit.doCheck(GET_HIDDEN, "passphrase.pin.repeat") ||
+ keyEdit.doCheck(GET_HIDDEN, "passphrase.ask") ||
+ keyEdit.doCheck(GET_HIDDEN, "passphrase.adminpin.new.ask")) {
+ ret.exitCode = 0;
+ ret.writeTxt = inputData.newPin;
+ }
+ else if (keyEdit.doCheck(GET_LINE, "cardutil.change_pin.menu")) {
+ ret.exitCode = 0;
+ ++inputData.pinStep;
+ if (inputData.pinStep == 1) {
+ ret.writeTxt = inputData.action.toString();
+ }
+ else {
+ ret.writeTxt = "Q";
+ }
+ }
+ else {
+ ret.exitCode = -1;
+ ret.quitNow = true;
+ EnigmailLog.ERROR("Unknown command prompt: " + keyEdit.getText() + "\n");
+ }
+}
+
+
+function addPhotoCallback(inputData, keyEdit, ret) {
+ ret.writeTxt = "";
+ ret.errorMsg = "";
+
+ if (keyEdit.doCheck(GET_LINE, "keyedit.prompt")) {
+ ret.exitCode = 0;
+ ret.writeTxt = "save";
+ ret.quitNow = true;
+ }
+ else if (keyEdit.doCheck(GET_LINE, "photoid.jpeg.add")) {
+ if (inputData.step === 0) {
+ ++inputData.step;
+ ret.exitCode = 0;
+ ret.writeTxt = inputData.file;
+ }
+ else {
+ ret.exitCode = -1;
+ ret.quitNow = true;
+ }
+ }
+ else if (keyEdit.doCheck(GET_BOOL, "photoid.jpeg.size")) {
+ ret.exitCode = 0;
+ ret.writeTxt = "Y"; // add large file
+ }
+ else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.adminpin.ask")) {
+ getPin(inputData.parent, EnigmailLocale.getString("enterAdminPin"), ret);
+ }
+ else if (keyEdit.doCheck(GET_HIDDEN, "passphrase.pin.ask")) {
+ getPin(inputData.parent, EnigmailLocale.getString("enterCardPin"), ret);
+ }
+ else {
+ ret.quitNow = true;
+ EnigmailLog.ERROR("Unknown command prompt: " + keyEdit.getText() + "\n");
+ ret.exitCode = -1;
+ }
+}
+
+function EnigCardAdminObserver(guiObserver, isDosLike) {
+ this._guiObserver = guiObserver;
+ this._isDosLike = isDosLike;
+}
+
+EnigCardAdminObserver.prototype = {
+ _guiObserver: null,
+ _failureCode: 0,
+
+ onDataAvailable: function(data) {
+ var ret = "";
+ EnigmailLog.DEBUG("keyEdit.jsm: enigCardAdminObserver.onDataAvailable: data=" + data + "\n");
+ if (this._isDosLike && data.indexOf("[GNUPG:] BACKUP_KEY_CREATED") === 0) {
+ data = data.replace(/\//g, "\\");
+ }
+ if (data.indexOf("[GNUPG:] SC_OP_FAILURE") >= 0) {
+ data = data.substr(23);
+ if (data == "2") {
+ data = "[GNUPG:] BAD_PASSPHRASE 0";
+ this._failureCode = 2;
+ }
+ else
+ this._failureCode = 1;
+ }
+ if (this._failureCode == 1) {
+ ret = "[GNUPG:] ENIGMAIL_FAILURE " + data;
+ }
+ if (this._guiObserver) {
+ this._guiObserver.onDataAvailable(data);
+ }
+ return ret;
+ }
+};
+
+function ChangePasswdObserver() {}
+
+ChangePasswdObserver.prototype = {
+ _failureCode: 0,
+ passphraseStatus: 0,
+
+ onDataAvailable: function(data) {
+ var ret = "";
+ EnigmailLog.DEBUG("keyEdit.jsm: ChangePasswdObserver.onDataAvailable: data=" + data + "\n");
+ if (this._failureCode) {
+ ret = "[GNUPG:] ENIGMAIL_FAILURE " + data;
+ }
+ if (data.indexOf("[GNUPG:] GOOD_PASSPHRASE") >= 0) {
+ this.passphraseStatus = 1;
+ }
+ else if (data.indexOf("[GNUPG:] BAD_PASSPHRASE") >= 0) {
+ this.passphraseStatus = -1;
+ }
+ return ret;
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/keyObj.jsm
@@ -0,0 +1,500 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["newEnigmailKeyObj"];
+
+
+/**
+ This module implements the EnigmailKeyObj class with the following members:
+
+ - keyId - 16 digits (8-byte) public key ID (/not/ preceeded with 0x)
+ - userId - main user ID
+ - fpr - fingerprint
+ - fprFormatted - a formatted version of the fingerprint followin the scheme .... .... ....
+ - expiry - Expiry date as printable string
+ - expiryTime - Expiry time as seconds after 01/01/1970
+ - created - Key creation date as printable string
+ - keyCreated - Key creation date/time as number
+ - keyTrust - key trust code as provided by GnuPG (calculated key validity)
+ - keyUseFor - key usage type as provided by GnuPG (key capabilities)
+ - ownerTrust - owner trust as provided by GnuPG
+ - photoAvailable - [Boolean] true if photo is available
+ - secretAvailable - [Boolean] true if secret key is available
+ - algoSym - public key algorithm type (String, e.g. RSA)
+ - keySize - size of public key
+ - type - "pub" or "grp"
+ - userIds - [Array]: - Contains ALL UIDs (including the primary UID)
+ * userId - User ID
+ * keyTrust - trust level of user ID
+ * uidFpr - fingerprint of the user ID
+ * type - one of "uid" (regular user ID), "uat" (photo)
+ * uatNum - photo number (starting with 0 for each key)
+ - subKeys - [Array]:
+ * keyId - subkey ID (16 digits (8-byte))
+ * expiry - Expiry date as printable string
+ * expiryTime - Expiry time as seconds after 01/01/1970
+ * created - Subkey creation date as printable string
+ * keyCreated - Subkey creation date/time as number
+ * keyTrust - key trust code as provided by GnuPG
+ * keyUseFor - key usage type as provided by GnuPG
+ * algoSym - subkey algorithm type (String, e.g. RSA)
+ * keySize - subkey size
+ * type - "sub"
+
+ - signatures - [Array]: list of signature objects
+ * userId
+ * uidLabel
+ * created
+ * fpr
+ * sigList: Array of object: { userId, created, signerKeyId, sigType, sigKnown }
+ - methods:
+ * hasSubUserIds
+ * getKeyExpiry
+ * getEncryptionValidity
+ * getSigningValidity
+ * getPubKeyValidity
+ * clone
+ * getMinimalPubKey
+ * getVirtualKeySize
+*/
+
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+const EnigmailKey = ChromeUtils.import("chrome://openpgp/content/modules/key.jsm").EnigmailKey;
+const EnigmailFuncs = ChromeUtils.import("chrome://openpgp/content/modules/funcs.jsm").EnigmailFuncs;
+const EnigmailTime = ChromeUtils.import("chrome://openpgp/content/modules/time.jsm").EnigmailTime;
+const EnigmailCryptoAPI = ChromeUtils.import("chrome://openpgp/content/modules/cryptoAPI.jsm").EnigmailCryptoAPI;
+
+function newEnigmailKeyObj(keyData) {
+ return new EnigmailKeyObj(keyData);
+}
+
+class EnigmailKeyObj {
+ constructor(keyData) {
+ this.keyId = "";
+ this.expiry = "";
+ this.expiryTime = 0;
+ this.created = "";
+ this.keyTrust = "";
+ this.keyUseFor = "";
+ this.ownerTrust = "";
+ this.algoSym = "";
+ this.keySize = "";
+ this.userId = "";
+ this.userIds = [];
+ this.subKeys = [];
+ this.fpr = "";
+ this.minimalKeyBlock = [];
+ this.photoAvailable = false;
+ this.secretAvailable = false;
+ this._sigList = null;
+
+ this.type = keyData.type;
+ if ("keyId" in keyData) this.keyId = keyData.keyId;
+ if ("expiryTime" in keyData) {
+ this.expiryTime = keyData.expiryTime;
+ this.expiry = EnigmailTime.getDateTime(keyData.expiryTime, true, false);
+ }
+
+ const ATTRS = [
+ "created", "keyCreated", "keyTrust", "keyUseFor", "ownerTrust", "algoSym", "keySize",
+ "userIds", "subKeys", "fpr", "secretAvailable", "photoAvailable", "userId"
+ ];
+ for (let i of ATTRS) {
+ if (i in keyData) {
+ this[i] = keyData[i];
+ }
+ }
+ }
+
+ /**
+ * gettter that returns a list of all signatures found on the key
+ *
+ * @return Array of Object, or null in case of error:
+ * - uid
+ * - uidLabel
+ * - creationDate
+ * - sigList: Array of object: { uid, creationDate, signerKeyId, sigType }
+ */
+ get signatures() {
+ if (this._sigList === null) {
+ const cApi = EnigmailCryptoAPI();
+ this._sigList = cApi.sync(cApi.getKeySignatures(this.keyId));
+ }
+
+ return this._sigList;
+ }
+
+ /**
+ * create a copy of the object
+ */
+ clone() {
+ let cp = new EnigmailKeyObj(["copy"]);
+ for (let i in this) {
+ if (i !== "signatures" && i !== "fprFormatted") {
+ // caution: don't try to evaluate this[i] if i==="signatures";
+ // it would immediately get all signatures for the key (slow!)
+ if (typeof this[i] !== "function") {
+ if (typeof this[i] === "object") {
+ cp[i] = EnigmailFuncs.cloneObj(this[i]);
+ } else
+ cp[i] = this[i];
+ }
+ }
+ }
+
+ return cp;
+ }
+
+ /**
+ * Does the key have secondary user IDs?
+ *
+ * @return: Boolean - true if yes; false if no
+ */
+ hasSubUserIds() {
+ let nUid = 0;
+ for (let i in this.userIds) {
+ if (this.userIds[i].type === "uid") ++nUid;
+ }
+
+ return nUid >= 2;
+ }
+
+ /**
+ * Get a formatted version of the fingerprint:
+ * 1234 5678 90AB CDEF .... ....
+ *
+ * @return String - the formatted fingerprint
+ */
+ get fprFormatted() {
+ let f = EnigmailKey.formatFpr(this.fpr);
+ if (f.length === 0) f = this.fpr;
+ return f;
+ }
+
+ /**
+ * Is the function to set owner trust available for the key?
+ * Requirements: The key is signed with at least medium validity level,
+ * or the secret key is available.
+ *
+ * @return Boolean true if yes
+ */
+ isOwnerTrustUseful() {
+ if (this.secretAvailable) return true;
+ if (this.keyTrust.search(/^[fu]/) === 0) return true;
+
+ return false;
+ }
+
+ /**
+ * Determine if the public key is valid. If not, return a description why it's not
+ *
+ * @return Object:
+ * - keyValid: Boolean (true if key is valid)
+ * - reason: String (explanation of invalidity)
+ */
+ getPubKeyValidity() {
+ let retVal = {
+ keyValid: false,
+ reason: ""
+ };
+ if (this.keyTrust.search(/r/i) >= 0) {
+ // public key revoked
+ retVal.reason = EnigmailLocale.getString("keyRing.pubKeyRevoked", [this.userId, "0x" + this.keyId]);
+ } else if (this.keyTrust.search(/e/i) >= 0) {
+ // public key expired
+ retVal.reason = EnigmailLocale.getString("keyRing.pubKeyExpired", [this.userId, "0x" + this.keyId]);
+ } else if (this.keyTrust.search(/d/i) >= 0 || this.keyUseFor.search(/D/i) >= 0) {
+ // public key disabled
+ retVal.reason = EnigmailLocale.getString("keyRing.keyDisabled", [this.userId, "0x" + this.keyId]);
+ } else if (this.keyTrust.search(/i/i) >= 0) {
+ // public key invalid
+ retVal.reason = EnigmailLocale.getString("keyRing.keyInvalid", [this.userId, "0x" + this.keyId]);
+ } else
+ retVal.keyValid = true;
+
+ return retVal;
+ }
+
+
+ /**
+ * Check whether a key can be used for signing and return a description of why not
+ *
+ * @return Object:
+ * - keyValid: Boolean (true if key is valid)
+ * - reason: String (explanation of invalidity)
+ */
+ getSigningValidity() {
+ let retVal = this.getPubKeyValidity();
+
+ if (!retVal.keyValid) return retVal;
+
+ if (!this.secretAvailable) {
+ retVal.reason = EnigmailLocale.getString("keyRing.noSecretKey", [this.userId, "0x" + this.keyId]);
+ retVal.keyValid = false;
+ } else if (this.keyUseFor.search(/S/) < 0) {
+ retVal.keyValid = false;
+
+ if (this.keyTrust.search(/u/i) < 0) {
+ // public key invalid
+ retVal.reason = EnigmailLocale.getString("keyRing.keyNotTrusted", [this.userId, "0x" + this.keyId]);
+ } else {
+ let expired = 0,
+ revoked = 0,
+ unusable = 0,
+ found = 0;
+ // public key is valid; check for signing subkeys
+ for (let sk in this.subKeys) {
+ if (this.subKeys[sk].keyUseFor.search(/[sS]/) >= 0) {
+ // found subkey usable for signing
+ ++found;
+ if (this.subKeys[sk].keyTrust.search(/e/i) >= 0) ++expired;
+ if (this.subKeys[sk].keyTrust.search(/r/i) >= 0) ++revoked;
+ if (this.subKeys[sk].keyTrust.search(/[di-]/i) >= 0 || this.subKeys[sk].keyUseFor.search(/D/) >= 0) ++unusable;
+ }
+ }
+
+ if (found > 0 && (expired > 0 || revoked > 0)) {
+ if (found === expired) {
+ retVal.reason = EnigmailLocale.getString("keyRing.signSubKeysExpired", [this.userId, "0x" + this.keyId]);
+ } else if (found === revoked) {
+ retVal.reason = EnigmailLocale.getString("keyRing.signSubKeysRevoked", [this.userId, "0x" + this.keyId]);
+ } else {
+ retVal.reason = EnigmailLocale.getString("keyRing.signSubKeysUnusable", [this.userId, "0x" + this.keyId]);
+ }
+ } else
+ retVal.reason = EnigmailLocale.getString("keyRing.pubKeyNotForSigning", [this.userId, "0x" + this.keyId]);
+ }
+ }
+
+ return retVal;
+ }
+
+ /**
+ * Check whether a key can be used for encryption and return a description of why not
+ *
+ * @return Object:
+ * - keyValid: Boolean (true if key is valid)
+ * - reason: String (explanation of invalidity)
+ */
+ getEncryptionValidity() {
+ let retVal = this.getPubKeyValidity();
+
+ if (!retVal.keyValid) return retVal;
+
+ if (this.keyUseFor.search(/E/) < 0) {
+ retVal.keyValid = false;
+
+ if (this.keyTrust.search(/u/i) < 0) {
+ // public key invalid
+ retVal.reason = EnigmailLocale.getString("keyRing.keyInvalid", [this.userId, "0x" + this.keyId]);
+ } else {
+ let expired = 0,
+ revoked = 0,
+ unusable = 0,
+ found = 0;
+ // public key is valid; check for encryption subkeys
+
+ for (let sk in this.subKeys) {
+ if (this.subKeys[sk].keyUseFor.search(/[eE]/) >= 0) {
+ // found subkey usable for signing
+ ++found;
+ if (this.subKeys[sk].keyTrust.search(/e/i) >= 0) ++expired;
+ if (this.subKeys[sk].keyTrust.search(/r/i) >= 0) ++revoked;
+ if (this.subKeys[sk].keyTrust.search(/[di-]/i) >= 0 || this.subKeys[sk].keyUseFor.search(/D/) >= 0) ++unusable;
+ }
+ }
+
+ if (found > 0 && (expired > 0 || revoked > 0)) {
+ if (found === expired) {
+ retVal.reason = EnigmailLocale.getString("keyRing.encSubKeysExpired", [this.userId, "0x" + this.keyId]);
+ } else if (found === revoked) {
+ retVal.reason = EnigmailLocale.getString("keyRing.encSubKeysRevoked", [this.userId, "0x" + this.keyId]);
+ } else {
+ retVal.reason = EnigmailLocale.getString("keyRing.encSubKeysUnusable", [this.userId, "0x" + this.keyId]);
+ }
+ } else
+ retVal.reason = EnigmailLocale.getString("keyRing.pubKeyNotForEncryption", [this.userId, "0x" + this.keyId]);
+ }
+ }
+
+ return retVal;
+ }
+
+ /**
+ * Determine the next expiry date of the key. This is either the public key expiry date,
+ * or the maximum expiry date of a signing or encryption subkey. I.e. this returns the next
+ * date at which the key cannot be used for signing and/or encryption anymore
+ *
+ * @return Number - The expiry date as seconds after 01/01/1970
+ */
+ getKeyExpiry() {
+ let expiryDate = Number.MAX_VALUE;
+ let encryption = -1;
+ let signing = -1;
+
+
+ // check public key expiry date
+ if (this.expiryTime > 0) {
+ expiryDate = this.expiryTime;
+ }
+
+ for (let sk in this.subKeys) {
+ if (this.subKeys[sk].keyUseFor.search(/[eE]/) >= 0) {
+ let expiry = this.subKeys[sk].expiryTime;
+ if (expiry === 0) expiry = Number.MAX_VALUE;
+ encryption = Math.max(encryption, expiry);
+ } else if (this.subKeys[sk].keyUseFor.search(/[sS]/) >= 0) {
+ let expiry = this.subKeys[sk].expiryTime;
+ if (expiry === 0) expiry = Number.MAX_VALUE;
+ signing = Math.max(signing, expiry);
+ }
+ }
+
+ if (expiryDate > encryption) {
+ if (this.keyUseFor.search(/[eE]/) < 0) {
+ expiryDate = encryption;
+ }
+ }
+
+ if (expiryDate > signing) {
+ if (this.keyUseFor.search(/[Ss]/) < 0) {
+ expiryDate = signing;
+ }
+ }
+
+ return expiryDate;
+ }
+
+ /**
+ * Export the minimum key for the public key object:
+ * public key, desired UID, newest signing/encryption subkey
+ *
+ * @param {String} emailAddr: [optional] email address of UID to extract. Use primary UID if null .
+ *
+ * @return Object:
+ * - exitCode (0 = success)
+ * - errorMsg (if exitCode != 0)
+ * - keyData: BASE64-encded string of key data
+ */
+
+ getMinimalPubKey(emailAddr) {
+ EnigmailLog.DEBUG("keyObj.jsm: EnigmailKeyObj.getMinimalPubKey: " + this.keyId + "\n");
+
+ if (emailAddr) {
+ try {
+ emailAddr = EnigmailFuncs.stripEmail(emailAddr.toLowerCase());
+ } catch (x) {
+ emailAddr = emailAddr.toLowerCase();
+ }
+
+
+ let foundUid = false,
+ uid = "";
+ for (let i in this.userIds) {
+ try {
+ uid = EnigmailFuncs.stripEmail(this.userIds[i].userId.toLowerCase());
+ } catch (x) {
+ uid = this.userIds[i].userId.toLowerCase();
+ }
+
+ if (uid == emailAddr) {
+ foundUid = true;
+ break;
+ }
+ }
+ if (!foundUid) emailAddr = false;
+ }
+
+ if (!emailAddr) {
+ emailAddr = this.userId;
+ }
+
+ try {
+ emailAddr = EnigmailFuncs.stripEmail(emailAddr.toLowerCase());
+ } catch (x) {
+ emailAddr = emailAddr.toLowerCase();
+ }
+
+ let newestSigningKey = 0,
+ newestEncryptionKey = 0,
+ subkeysArr = null;
+
+ // search for valid subkeys
+ for (let sk in this.subKeys) {
+ if ("indDre".indexOf(this.subKeys[sk].keyTrust) < 0) {
+ if (this.subKeys[sk].keyUseFor.search(/[sS]/) >= 0) {
+ // found signing subkey
+ if (this.subKeys[sk].keyCreated > newestSigningKey) newestSigningKey = this.subKeys[sk].keyCreated;
+ }
+ if (this.subKeys[sk].keyUseFor.search(/[eE]/) >= 0) {
+ // found encryption subkey
+ if (this.subKeys[sk].keyCreated > newestEncryptionKey) newestEncryptionKey = this.subKeys[sk].keyCreated;
+ }
+ }
+ }
+
+ if (newestSigningKey > 0 && newestEncryptionKey > 0) {
+ subkeysArr = [newestEncryptionKey, newestSigningKey];
+ }
+
+ if (!(emailAddr in this.minimalKeyBlock)) {
+ const cApi = EnigmailCryptoAPI();
+ this.minimalKeyBlock[emailAddr] = cApi.sync(cApi.getMinimalPubKey(this.fpr, emailAddr, subkeysArr));
+ }
+ return this.minimalKeyBlock[emailAddr];
+ }
+
+ /**
+ * Obtain a "virtual" key size that allows to compare different algorithms with each other
+ * e.g. elliptic curve keys have small key sizes with high cryptographic strength
+ *
+ *
+ * @return Number: a virtual size
+ */
+ getVirtualKeySize() {
+ EnigmailLog.DEBUG("keyObj.jsm: EnigmailKeyObj.getVirtualKeySize: " + this.keyId + "\n");
+
+ switch (this.algoSym) {
+ case "DSA":
+ return this.keySize / 2;
+ case "ECDSA":
+ return this.keySize * 8;
+ case "EDDSA":
+ return this.keySize * 32;
+ default:
+ return this.keySize;
+ }
+ }
+
+
+ /**
+ * Get a file object holding the photo of a key
+ *
+ * @param {Number} photoNumber: number of the photo on the key, starting with 0
+ *
+ * @return {nsIFile} object or null in case no data / error.
+ */
+ getPhotoFile(photoNumber) {
+ const cApi = EnigmailCryptoAPI();
+ return cApi.sync(cApi.getPhotoFile(this.fpr, photoNumber));
+ }
+
+ /**
+ * @param {Boolean} minimalKey if true, reduce key to minimum required
+ *
+ * @return {Object}:
+ * - {Number} exitCode: result code (0: OK)
+ * - {String} keyData: ASCII armored key data material
+ * - {String} errorMsg: error message in case exitCode !== 0
+ */
+ getSecretKey(minimalKey) {
+ const cApi = EnigmailCryptoAPI();
+ return cApi.sync(cApi.extractSecretKey(this.fpr, minimalKey));
+ }
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/keyRefreshService.jsm
@@ -0,0 +1,153 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+const EXPORTED_SYMBOLS = ["EnigmailKeyRefreshService"];
+
+
+
+
+
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailKeyRing = ChromeUtils.import("chrome://openpgp/content/modules/keyRing.jsm").EnigmailKeyRing;
+const EnigmailRNG = ChromeUtils.import("chrome://openpgp/content/modules/rng.jsm").EnigmailRNG;
+const EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs.jsm").EnigmailPrefs;
+const EnigmailKeyServer = ChromeUtils.import("chrome://openpgp/content/modules/keyserver.jsm").EnigmailKeyServer;
+const EnigmailKeyserverURIs = ChromeUtils.import("chrome://openpgp/content/modules/keyserverUris.jsm").EnigmailKeyserverURIs;
+
+const IOSERVICE_CONTRACTID = "@mozilla.org/network/io-service;1";
+const ONE_HOUR_IN_MILLISEC = 60 * 60 * 1000;
+
+let gTimer = null;
+
+function getTimer() {
+ if (gTimer === null) gTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ return gTimer;
+}
+
+const HOURS_PER_WEEK_ENIGMAIL_IS_ON_PREF = "hoursPerWeekEnigmailIsOn";
+const SECONDS_MIN_DELAY = "refreshMinDelaySeconds";
+
+function calculateMaxTimeForRefreshInMilliseconds(totalPublicKeys) {
+ const millisecondsAvailableForRefresh = EnigmailPrefs.getPref(HOURS_PER_WEEK_ENIGMAIL_IS_ON_PREF) * ONE_HOUR_IN_MILLISEC;
+ return Math.floor(millisecondsAvailableForRefresh / totalPublicKeys);
+}
+
+function calculateWaitTimeInMilliseconds(totalPublicKeys) {
+ const randomNumber = EnigmailRNG.generateRandomUint32();
+ const maxTimeForRefresh = calculateMaxTimeForRefreshInMilliseconds(totalPublicKeys);
+ const minDelay = EnigmailPrefs.getPref(SECONDS_MIN_DELAY) * 1000;
+
+ EnigmailLog.DEBUG("keyRefreshService.jsm: Wait time = random number: " + randomNumber + " % max time for refresh: " + maxTimeForRefresh + "\n");
+
+ let millisec = randomNumber % maxTimeForRefresh;
+ if (millisec < minDelay) {
+ millisec += minDelay;
+ }
+
+ EnigmailLog.DEBUG("keyRefreshService.jsm: Time until next refresh in milliseconds: " + millisec + "\n");
+
+ return millisec;
+}
+
+function refreshKey() {
+ const timer = getTimer();
+ refreshWith(EnigmailKeyServer, timer, true);
+}
+
+function restartTimerInOneHour(timer) {
+ timer.initWithCallback(refreshKey,
+ ONE_HOUR_IN_MILLISEC,
+ Ci.nsITimer.TYPE_ONE_SHOT);
+}
+
+function setupNextRefresh(timer, waitTime) {
+ timer.initWithCallback(refreshKey,
+ waitTime,
+ Ci.nsITimer.TYPE_ONE_SHOT);
+}
+
+function logMissingInformation(keyIdsExist, validKeyserversExist) {
+ if (!keyIdsExist) {
+ EnigmailLog.DEBUG("keyRefreshService.jsm: No keys available to refresh yet. Will recheck in an hour.\n");
+ }
+ if (!validKeyserversExist) {
+ EnigmailLog.DEBUG("keyRefreshService.jsm: Either no keyservers exist or the protocols specified are invalid. Will recheck in an hour.\n");
+ }
+}
+
+function getRandomKeyId(randomNumber) {
+ const keyRingLength = EnigmailKeyRing.getAllKeys().keyList.length;
+
+ if (keyRingLength === 0) {
+ return null;
+ }
+
+ return EnigmailKeyRing.getAllKeys().keyList[randomNumber % keyRingLength].keyId;
+}
+
+function refreshKeyIfReady(keyserver, readyToRefresh, keyId) {
+ if (readyToRefresh) {
+ EnigmailLog.DEBUG("keyRefreshService.jsm: refreshing key ID " + keyId + "\n");
+ return keyserver.download(keyId);
+ }
+ else {
+ return Promise.resolve(0);
+ }
+}
+
+async function refreshWith(keyserver, timer, readyToRefresh) {
+ const keyId = getRandomKeyId(EnigmailRNG.generateRandomUint32());
+ const keyIdsExist = keyId !== null;
+ const validKeyserversExist = EnigmailKeyserverURIs.validKeyserversExist();
+ const ioService = Cc[IOSERVICE_CONTRACTID].getService(Ci.nsIIOService);
+
+ if (keyIdsExist && validKeyserversExist) {
+ if (ioService && (!ioService.offline)) {
+ // don't try to refresh if we are offline
+ await refreshKeyIfReady(keyserver, readyToRefresh, keyId);
+ }
+ else {
+ EnigmailLog.DEBUG("keyRefreshService.jsm: offline - not refreshing any key\n");
+ }
+ const waitTime = calculateWaitTimeInMilliseconds(EnigmailKeyRing.getAllKeys().keyList.length);
+ setupNextRefresh(timer, waitTime);
+ }
+ else {
+ logMissingInformation(keyIdsExist, validKeyserversExist);
+ restartTimerInOneHour(timer);
+ }
+}
+
+/**
+ * Starts a process to continuously refresh keys on a random time interval and in random order.
+ *
+ * The default time period for all keys to be refreshed is one week, although the user can specifically set this in their preferences
+ * The wait time to refresh the next key is selected at random, from a range of zero milliseconds to the maximum time to refresh a key
+ *
+ * The maximum time to refresh a single key is calculated by averaging the total refresh time by the total number of public keys to refresh
+ * For example, if a user has 12 public keys to refresh, the maximum time to refresh a single key (by default) will be: milliseconds per week divided by 12
+ *
+ * This service does not keep state, it will restart each time Enigmail is initialized.
+ *
+ * @param keyserver | dependency injected for testability
+ */
+function start(keyserver) {
+ if (EnigmailPrefs.getPref("keyRefreshOn")) {
+ EnigmailLog.DEBUG("keyRefreshService.jsm: Started\n");
+ const timer = getTimer();
+ refreshWith(keyserver, timer, false);
+ }
+}
+
+/*
+ This module intializes the continuous key refresh functionality. This includes randomly selecting th key to refresh and the timing to wait between each refresh
+*/
+
+var EnigmailKeyRefreshService = {
+ start: start
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/keyRing.jsm
@@ -0,0 +1,1236 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailKeyRing"];
+
+const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailExecution = ChromeUtils.import("chrome://openpgp/content/modules/execution.jsm").EnigmailExecution;
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
+const EnigmailGpg = ChromeUtils.import("chrome://openpgp/content/modules/gpg.jsm").EnigmailGpg;
+const EnigmailTrust = ChromeUtils.import("chrome://openpgp/content/modules/trust.jsm").EnigmailTrust;
+const EnigmailArmor = ChromeUtils.import("chrome://openpgp/content/modules/armor.jsm").EnigmailArmor;
+const EnigmailTime = ChromeUtils.import("chrome://openpgp/content/modules/time.jsm").EnigmailTime;
+const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
+const subprocess = ChromeUtils.import("chrome://openpgp/content/modules/subprocess.jsm").subprocess;
+const EnigmailLazy = ChromeUtils.import("chrome://openpgp/content/modules/lazy.jsm").EnigmailLazy;
+const newEnigmailKeyObj = ChromeUtils.import("chrome://openpgp/content/modules/keyObj.jsm").newEnigmailKeyObj;
+const EnigmailTimer = ChromeUtils.import("chrome://openpgp/content/modules/timer.jsm").EnigmailTimer;
+const Services = ChromeUtils.import("resource://gre/modules/Services.jsm").Services;
+const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
+const EnigmailCryptoAPI = ChromeUtils.import("chrome://openpgp/content/modules/cryptoAPI.jsm").EnigmailCryptoAPI;
+
+
+const getDialog = EnigmailLazy.loader("enigmail/dialog.jsm", "EnigmailDialog");
+const getWindows = EnigmailLazy.loader("enigmail/windows.jsm", "EnigmailWindows");
+const getKeyUsability = EnigmailLazy.loader("enigmail/keyUsability.jsm", "EnigmailKeyUsability");
+
+const DEFAULT_FILE_PERMS = 0o600;
+
+let gKeygenProcess = null;
+let gKeyListObj = null;
+let gKeyIndex = [];
+let gSubkeyIndex = [];
+let gKeyCheckDone = false;
+let gLoadingKeys = false;
+
+/*
+
+ This module operates with a Key Store (array) containing objects with the following properties:
+
+ * keyList [Array] of EnigmailKeyObj
+
+ * keySortList [Array]: used for quickly sorting the keys
+ - userId (in lower case)
+ - keyId
+ - keyNum
+ * trustModel: [String]. One of:
+ - p: pgp/classical
+ - t: always trust
+ - a: auto (:0) (default, currently pgp/classical)
+ - T: TOFU
+ - TP: TOFU+PGP
+
+*/
+
+const TRUSTLEVELS_SORTED = EnigmailTrust.trustLevelsSorted();
+
+var EnigmailKeyRing = {
+
+ /**
+ * Get the complete list of all public keys, optionally sorted by a column
+ *
+ * @param win - optional |object| holding the parent window for displaying error messages
+ * @param sortColumn - optional |string| containing the column name for sorting. One of:
+ * userid, keyid, keyidshort, fpr, keytype, validity, trust, expiry
+ * @param sortDirection - |number| 1 = ascending / -1 = descending
+ *
+ * @return keyListObj - |object| { keyList, keySortList } (see above)
+ */
+ getAllKeys: function(win, sortColumn, sortDirection) {
+ if (gKeyListObj.keySortList.length === 0) {
+ loadKeyList(win, sortColumn, sortDirection);
+ getWindows().keyManReloadKeys();
+ if (!gKeyCheckDone) {
+ gKeyCheckDone = true;
+ runKeyUsabilityCheck();
+ }
+ }
+ else {
+ if (sortColumn) {
+ gKeyListObj.keySortList.sort(getSortFunction(sortColumn.toLowerCase(), gKeyListObj, sortDirection));
+ }
+ }
+
+ return gKeyListObj;
+ },
+
+ /**
+ * get a list of all (valid, usable) keys that have a secret key
+ *
+ * @param Boolean onlyValidKeys: if true, only filter valid usable keys
+ *
+ * @return Array of KeyObjects containing the found keys (sorted by userId)
+ **/
+
+ getAllSecretKeys: function(onlyValidKeys = false) {
+ EnigmailLog.DEBUG("keyRing.jsm: getAllSecretKeys()\n");
+
+ let res = [];
+
+ this.getAllKeys(); // ensure keylist is loaded;
+
+ if (!onlyValidKeys) {
+ for (let key of gKeyListObj.keyList) {
+ if (key.secretAvailable) res.push(key);
+ }
+ }
+ else {
+ for (let key of gKeyListObj.keyList) {
+ if (key.secretAvailable && key.keyUseFor.search(/D/) < 0) {
+ // key is not disabled and _usable_ for encryption signing and certification
+ if (key.keyUseFor.search(/E/) >= 0 &&
+ key.keyUseFor.search(/S/) >= 0 &&
+ key.keyUseFor.search(/C/) >= 0) {
+ res.push(key);
+ }
+ }
+ }
+ }
+
+ res.sort(function(a, b) {
+ return a.userId == b.userId ? (a.keyId < b.keyId ? -1 : 1) : (a.userId.toLowerCase() < b.userId.toLowerCase() ? -1 : 1);
+ });
+
+ return res;
+ },
+
+
+ /**
+ * get 1st key object that matches a given key ID or subkey ID
+ *
+ * @param keyId - String: key Id with 16 characters (preferred) or 8 characters),
+ * or fingerprint (40 or 32 characters).
+ * Optionally preceeded with "0x"
+ * @param noLoadKeys - Boolean [optional]: do not try to load the key list first
+ *
+ * @return Object - found KeyObject or null if key not found
+ */
+ getKeyById: function(keyId, noLoadKeys) {
+ EnigmailLog.DEBUG("keyRing.jsm: getKeyById: " + keyId + "\n");
+ let s;
+
+ if (!keyId) return null;
+
+ if (keyId.search(/^0x/) === 0) {
+ keyId = keyId.substr(2);
+ }
+
+ if (!noLoadKeys) {
+ this.getAllKeys(); // ensure keylist is loaded;
+ }
+
+ let keyObj = gKeyIndex[keyId];
+
+ if (keyObj === undefined) {
+ keyObj = gSubkeyIndex[keyId];
+ }
+
+ return keyObj !== undefined ? keyObj : null;
+ },
+
+ /**
+ * get all key objects that match a given user ID
+ *
+ * @param searchTerm - String: a regular expression to match against all UIDs of the keys.
+ * The search is always performed case-insensitively
+ * An empty string will return no result
+ * @param onlyValidUid - Boolean: if true (default), invalid (e.g. revoked) UIDs are not matched
+ *
+ * @return Array of KeyObjects with the found keys (array length is 0 if no key found)
+ */
+ getKeysByUserId: function(searchTerm, onlyValidUid = true) {
+ EnigmailLog.DEBUG("keyRing.jsm: getKeysByUserId: '" + searchTerm + "'\n");
+ let s = new RegExp(searchTerm, "i");
+
+ let res = [];
+
+ this.getAllKeys(); // ensure keylist is loaded;
+
+ if (searchTerm === "") return res;
+
+ for (let i in gKeyListObj.keyList) {
+ let k = gKeyListObj.keyList[i];
+
+ for (let j in k.userIds) {
+ if (k.userIds[j].type === "uid" && k.userIds[j].userId.search(s) >= 0) {
+ if (!onlyValidUid || (!EnigmailTrust.isInvalid(k.userIds[j].keyTrust))) {
+ res.push(k);
+ continue;
+ }
+ }
+ }
+ }
+
+ return res;
+ },
+
+ /**
+ * Specialized function for getSecretKeyByUserId() that takes into account
+ * the specifics of email addresses in UIDs.
+ *
+ * @param emailAddr: String - email address to search for without any angulars
+ * or names
+ *
+ * @return KeyObject with the found key, or null if no key found
+ */
+ getSecretKeyByEmail: function(emailAddr) {
+ // sanitize email address
+ emailAddr = emailAddr.replace(/([\.\[\]\-\\])/g, "\\$1");
+
+ let searchTerm = "(<" + emailAddr + ">| " + emailAddr + "$|^" + emailAddr + "$)";
+
+ return this.getSecretKeyByUserId(searchTerm);
+ },
+
+ /**
+ * get the "best" possible secret key for a given user ID
+ *
+ * @param searchTerm - String: a regular expression to match against all UIDs of the keys.
+ * The search is always performed case-insensitively
+ * @return KeyObject with the found key, or null if no key found
+ */
+ getSecretKeyByUserId: function(searchTerm) {
+ EnigmailLog.DEBUG("keyRing.jsm: getSecretKeyByUserId: '" + searchTerm + "'\n");
+ let keyList = this.getKeysByUserId(searchTerm, true);
+
+ let foundKey = null;
+
+ for (let key of keyList) {
+ if (key.secretAvailable && key.getEncryptionValidity().keyValid && key.getSigningValidity().keyValid) {
+ if (!foundKey) {
+ foundKey = key;
+ }
+ else {
+ // prefer RSA or DSA over ECC (long-term: change this once ECC keys are widely supported)
+ if (foundKey.algoSym === key.algoSym && foundKey.keySize === key.keySize) {
+ if (key.expiryTime > foundKey.expiryTime)
+ foundKey = key;
+ }
+ else if (foundKey.algoSym.search(/^(DSA|RSA)$/) < 0 && key.algoSym.search(/^(DSA|RSA)$/) === 0) {
+ foundKey = key;
+ }
+ else {
+ if (key.getVirtualKeySize() > foundKey.getVirtualKeySize())
+ foundKey = key;
+ }
+ }
+ }
+ }
+ return foundKey;
+ },
+
+ /**
+ * get a list of keys for a given set of (sub-) key IDs
+ *
+ * @param keyIdList: Array of key IDs
+ OR String, with space-separated list of key IDs
+ */
+ getKeyListById: function(keyIdList) {
+ EnigmailLog.DEBUG("keyRing.jsm: getKeyListById: '" + keyIdList + "'\n");
+ let keyArr;
+ if (typeof keyIdList === "string") {
+ keyArr = keyIdList.split(/ +/);
+ }
+ else {
+ keyArr = keyIdList;
+ }
+
+ let ret = [];
+ for (let i in keyArr) {
+ let r = this.getKeyById(keyArr[i]);
+ if (r) ret.push(r);
+ }
+
+ return ret;
+ },
+
+ importKeyFromFile: function(inputFile, errorMsgObj, importedKeysObj) {
+ EnigmailLog.DEBUG("keyRing.jsm: EnigmailKeyRing.importKeyFromFile: fileName=" + inputFile.path + "\n");
+
+ const cApi = EnigmailCryptoAPI();
+ let res = cApi.sync(cApi.importKeyFromFile(inputFile));
+ if (importedKeysObj) {
+ importedKeysObj.value = res.importedKeys.join(";");
+ }
+
+ if (!res) return 1;
+
+ if (res.importedKeys.length > 0) {
+ EnigmailKeyRing.updateKeys(res.importedKeys);
+ }
+ else if (res.importSum > res.importUnchanged) {
+ EnigmailKeyRing.clearCache();
+ }
+
+ return res.exitCode;
+ },
+
+
+ /**
+ * empty the key cache, such that it will get loaded next time it is accessed
+ *
+ * no input or return values
+ */
+ clearCache: function() {
+ EnigmailLog.DEBUG("keyRing.jsm: EnigmailKeyRing.clearCache\n");
+ gKeyListObj = {
+ keyList: [],
+ keySortList: []
+ };
+
+ gKeyIndex = [];
+ gSubkeyIndex = [];
+ },
+
+ /**
+ * Check if the cache is empty
+ *
+ * @return Boolean: true: cache cleared
+ */
+ getCacheEmpty: function() {
+ return (gKeyIndex.length === 0);
+ },
+
+ /**
+ * Get a list of UserIds for a given key.
+ * Only the Only UIDs with highest trust level are returned.
+ *
+ * @param String keyId key, optionally preceeded with 0x
+ *
+ * @return Array of String: list of UserIds
+ */
+ getValidUids: function(keyId) {
+ let r = [];
+ let keyObj = this.getKeyById(keyId);
+
+ if (keyObj) {
+ const TRUSTLEVELS_SORTED = EnigmailTrust.trustLevelsSorted();
+ let hideInvalidUid = true;
+ let maxTrustLevel = TRUSTLEVELS_SORTED.indexOf(keyObj.keyTrust);
+
+ if (EnigmailTrust.isInvalid(keyObj.keyTrust)) {
+ // pub key not valid (anymore)-> display all UID's
+ hideInvalidUid = false;
+ }
+
+ for (let i in keyObj.userIds) {
+ if (keyObj.userIds[i].type !== "uat") {
+ if (hideInvalidUid) {
+ let thisTrust = TRUSTLEVELS_SORTED.indexOf(keyObj.userIds[i].keyTrust);
+ if (thisTrust > maxTrustLevel) {
+ r = [keyObj.userIds[i].userId];
+ maxTrustLevel = thisTrust;
+ }
+ else if (thisTrust === maxTrustLevel) {
+ r.push(keyObj.userIds[i].userId);
+ }
+ // else do not add uid
+ }
+ else if (!EnigmailTrust.isInvalid(keyObj.userIds[i].keyTrust) || !hideInvalidUid) {
+ // UID valid OR key not valid, but invalid keys allowed
+ r.push(keyObj.userIds[i].userId);
+ }
+ }
+ }
+ }
+
+ return r;
+ },
+
+ /**
+ * Export public and possibly secret key(s) to a file
+ *
+ * @param includeSecretKey Boolean - if true, secret keys are exported
+ * @param userId String - space or comma separated list of keys to export. Specification by
+ * key ID, fingerprint, or userId
+ * @param outputFile String or nsIFile - output file name or Object - or NULL
+ * @param exitCodeObj Object - o.value will contain exit code
+ * @param errorMsgObj Object - o.value will contain error message from GnuPG
+ *
+ * @return String - if outputFile is NULL, the key block data; "" if a file is written
+ */
+ extractKey: function(includeSecretKey, userId, outputFile, exitCodeObj, errorMsgObj) {
+ EnigmailLog.DEBUG("keyRing.jsm: EnigmailKeyRing.extractKey: " + userId + "\n");
+ let args = EnigmailGpg.getStandardArgs(true).concat(["-a", "--export"]);
+
+ if (userId) {
+ args = args.concat(userId.split(/[ ,\t]+/));
+ }
+
+ const cmdErrorMsgObj = {};
+ let keyBlock = EnigmailExecution.execCmd(EnigmailGpg.agentPath, args, "", exitCodeObj, {}, {}, cmdErrorMsgObj);
+
+ if ((exitCodeObj.value === 0) && !keyBlock) {
+ exitCodeObj.value = -1;
+ }
+
+ if (exitCodeObj.value !== 0) {
+ errorMsgObj.value = EnigmailLocale.getString("failKeyExtract");
+
+ if (cmdErrorMsgObj.value) {
+ errorMsgObj.value += "\n" + EnigmailFiles.formatCmdLine(EnigmailGpg.agentPath, args);
+ errorMsgObj.value += "\n" + cmdErrorMsgObj.value;
+ }
+
+ return "";
+ }
+
+ if (includeSecretKey) {
+
+ let keyList;
+ if (userId) {
+ keyList = userId.split(/[ ,\t]+/);
+ }
+ else {
+ keyList = this.getAllSecretKeys().map(keyObj => {
+ return keyObj.keyId;
+ });
+ }
+ let secKeyBlock = keyList.map(uid => {
+ let keyObj = EnigmailKeyRing.getKeyById(uid);
+ if (keyObj) return keyObj.getSecretKey(false).keyData;
+
+ let k = EnigmailKeyRing.getKeysByUserId(uid);
+ if (k && k.length > 0) {
+ return k.map(keyObj => {
+ return keyObj.getSecretKey(false).keyData;
+ }).join("\n");
+ }
+ else {
+ return "";
+ }
+ }).join("\n");
+
+ keyBlock += "\n" + secKeyBlock;
+ }
+
+ if (outputFile) {
+ if (!EnigmailFiles.writeFileContents(outputFile, keyBlock, DEFAULT_FILE_PERMS)) {
+ exitCodeObj.value = -1;
+ errorMsgObj.value = EnigmailLocale.getString("fileWriteFailed", [outputFile]);
+ }
+ return "";
+ }
+ return keyBlock;
+ },
+
+ /**
+ * Export the ownertrust database from GnuPG
+ * @param outputFile String or nsIFile - output file name or Object - or NULL
+ * @param exitCodeObj Object - o.value will contain exit code
+ * @param errorMsgObj Object - o.value will contain error message from GnuPG
+ *
+ * @return String - if outputFile is NULL, the key block data; "" if a file is written
+ */
+ extractOwnerTrust: function(outputFile, exitCodeObj, errorMsgObj) {
+ let args = EnigmailGpg.getStandardArgs(true).concat(["--export-ownertrust"]);
+
+ let trustData = EnigmailExecution.execCmd(EnigmailGpg.agentPath, args, "", exitCodeObj, {}, {}, errorMsgObj);
+
+ if (outputFile) {
+ if (!EnigmailFiles.writeFileContents(outputFile, trustData, DEFAULT_FILE_PERMS)) {
+ exitCodeObj.value = -1;
+ errorMsgObj.value = EnigmailLocale.getString("fileWriteFailed", [outputFile]);
+ }
+ return "";
+ }
+
+ return trustData;
+ },
+
+ /**
+ * Import the ownertrust database into GnuPG
+ * @param inputFile String or nsIFile - input file name or Object - or NULL
+ * @param errorMsgObj Object - o.value will contain error message from GnuPG
+ *
+ * @return exit code
+ */
+ importOwnerTrust: function(inputFile, errorMsgObj) {
+ let args = EnigmailGpg.getStandardArgs(true).concat(["--import-ownertrust"]);
+
+ let exitCodeObj = {};
+ try {
+ let trustData = EnigmailFiles.readFile(inputFile);
+ EnigmailExecution.execCmd(EnigmailGpg.agentPath, args, trustData, exitCodeObj, {}, {}, errorMsgObj);
+ }
+ catch (ex) {}
+
+ return exitCodeObj.value;
+ },
+
+ /**
+ * import key from provided key data (synchronous)
+ *
+ * @param parent nsIWindow
+ * @param isInteractive Boolean - if true, display confirmation dialog
+ * @param keyBlock String - data containing key
+ * @param keyId String - key ID expected to import (no meaning)
+ * @param errorMsgObj Object - o.value will contain error message from GnuPG
+ * @param importedKeysObj Object - [OPTIONAL] o.value will contain an array of the FPRs imported
+ * @param minimizeKey Boolean - [OPTIONAL] minimize key for importing
+ * @param limitedUids Array<String> - [OPTIONAL] restrict importing the key(s) to a given set of UIDs
+ *
+ * @return Integer - exit code:
+ * ExitCode == 0 => success
+ * ExitCode > 0 => error
+ * ExitCode == -1 => Cancelled by user
+ */
+ importKey: function(parent, isInteractive, keyBlock, keyId, errorMsgObj, importedKeysObj, minimizeKey = false, limitedUids = []) {
+ const cApi = EnigmailCryptoAPI();
+ return cApi.sync(this.importKeyAsync(parent, isInteractive, keyBlock, keyId, errorMsgObj, importedKeysObj, minimizeKey, limitedUids));
+ },
+
+ /**
+ * import key from provided key data
+ *
+ * @param parent nsIWindow
+ * @param isInteractive Boolean - if true, display confirmation dialog
+ * @param keyBlock String - data containing key
+ * @param keyId String - key ID expected to import (no meaning)
+ * @param errorMsgObj Object - o.value will contain error message from GnuPG
+ * @param importedKeysObj Object - [OPTIONAL] o.value will contain an array of the FPRs imported
+ * @param minimizeKey Boolean - [OPTIONAL] minimize key for importing
+ * @param limitedUids Array<String> - [OPTIONAL] restrict importing the key(s) to a given set of UIDs
+ *
+ * @return Integer - exit code:
+ * ExitCode == 0 => success
+ * ExitCode > 0 => error
+ * ExitCode == -1 => Cancelled by user
+ */
+ importKeyAsync: async function(parent, isInteractive, keyBlock, keyId, errorMsgObj, importedKeysObj, minimizeKey = false, limitedUids = []) {
+ EnigmailLog.DEBUG(`keyRing.jsm: EnigmailKeyRing.importKeyAsync('${keyId}', ${isInteractive}, ${minimizeKey})\n`);
+
+ const beginIndexObj = {};
+ const endIndexObj = {};
+ const blockType = EnigmailArmor.locateArmoredBlock(keyBlock, 0, "", beginIndexObj, endIndexObj, {});
+ if (!blockType) {
+ errorMsgObj.value = EnigmailLocale.getString("noPGPblock");
+ return 1;
+ }
+
+ if (blockType.search(/^(PUBLIC|PRIVATE) KEY BLOCK$/) !== 0) {
+ errorMsgObj.value = EnigmailLocale.getString("notFirstBlock");
+ return 1;
+ }
+
+ const pgpBlock = keyBlock.substr(beginIndexObj.value,
+ endIndexObj.value - beginIndexObj.value + 1);
+
+ if (isInteractive) {
+ if (!(getDialog().confirmDlg(parent, EnigmailLocale.getString("importKeyConfirm"), EnigmailLocale.getString("keyMan.button.import")))) {
+ errorMsgObj.value = EnigmailLocale.getString("failCancel");
+ return -1;
+ }
+ }
+
+ let args = EnigmailGpg.getStandardArgs(false).concat(["--no-verbose", "--status-fd", "2"]);
+ if (minimizeKey) {
+ args = args.concat(["--import-options", "import-minimal"]);
+ }
+
+ if (limitedUids.length > 0 && EnigmailGpg.getGpgFeature("export-specific-uid")) {
+ let filter = limitedUids.map(i => {
+ return `mbox =~ ${i}`;
+ }).join(" || ");
+
+ args.push("--import-filter");
+ args.push(`keep-uid=${filter}`);
+ }
+ args = args.concat(["--no-auto-check-trustdb", "--import"]);
+
+ const res = await EnigmailExecution.execAsync(EnigmailGpg.agentPath, args, pgpBlock);
+
+ const statusMsg = res.statusMsg;
+
+ if (!importedKeysObj) {
+ importedKeysObj = {};
+ }
+ importedKeysObj.value = [];
+
+ let exitCode = 1;
+ if (statusMsg && (statusMsg.search(/^IMPORT_RES /m) > -1)) {
+ let importRes = statusMsg.match(/^IMPORT_RES ([0-9]+) ([0-9]+) ([0-9]+) 0 ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)/m);
+
+ if (importRes !== null) {
+ let secCount = parseInt(importRes[9], 10); // number of secret keys found
+ let secImported = parseInt(importRes[10], 10); // number of secret keys imported
+ let secDups = parseInt(importRes[11], 10); // number of secret keys already on the keyring
+
+ if (secCount !== secImported + secDups) {
+ EnigmailKeyRing.clearCache();
+ errorMsgObj.value = EnigmailLocale.getString("import.secretKeyImportError");
+ return 1;
+ }
+ }
+
+ exitCode = 0;
+ // Normal return
+ if (statusMsg.search(/^IMPORT_OK /m) > -1) {
+ let l = statusMsg.split(/\r|\n/);
+ for (let i = 0; i < l.length; i++) {
+ const matches = l[i].match(/^(IMPORT_OK [0-9]+ )(([0-9a-fA-F]{8}){2,5})/);
+ if (matches && (matches.length > 2)) {
+ EnigmailLog.DEBUG("enigmail.js: Enigmail.importKey: IMPORTED 0x" + matches[2] + "\n");
+ importedKeysObj.value.push(matches[2]);
+ }
+ }
+
+ if (importedKeysObj.value.length > 0) {
+ EnigmailKeyRing.updateKeys(importedKeysObj.value);
+ }
+ }
+ }
+
+ return exitCode;
+ },
+
+ isGeneratingKey: function() {
+ return gKeygenProcess !== null;
+ },
+
+ /**
+ * Generate a new key pair with GnuPG
+ *
+ * @name: String - name part of UID
+ * @comment: String - comment part of UID (brackets are added)
+ * @comment: String - email part of UID (<> will be added)
+ * @expiryDate: Number - Unix timestamp of key expiry date; 0 if no expiry
+ * @keyLength: Number - size of key in bytes (e.g 4096)
+ * @keyType: String - RSA or ECC
+ * @passphrase: String - password; null if no password
+ * @listener: Object - {
+ * function onDataAvailable(data) {...},
+ * function onStopRequest(exitCode) {...}
+ * }
+ *
+ * @return: handle to process
+ */
+ generateKey: function(name, comment, email, expiryDate, keyLength, keyType,
+ passphrase, listener) {
+ EnigmailLog.WRITE("keyRing.jsm: generateKey:\n");
+
+ if (EnigmailKeyRing.isGeneratingKey()) {
+ // key generation already ongoing
+ throw Components.results.NS_ERROR_FAILURE;
+ }
+
+ const args = EnigmailGpg.getStandardArgs(true).concat(["--gen-key"]);
+
+ EnigmailLog.CONSOLE(EnigmailFiles.formatCmdLine(EnigmailGpg.agentPath, args));
+
+ let inputData = "%echo Generating key\nKey-Type: ";
+
+ switch (keyType) {
+ case "RSA":
+ inputData += "RSA\nKey-Usage: sign,auth\nKey-Length: " + keyLength;
+ inputData += "\nSubkey-Type: RSA\nSubkey-Usage: encrypt\nSubkey-Length: " + keyLength + "\n";
+ break;
+ case "ECC":
+ inputData += "EDDSA\nKey-Curve: Ed25519\nKey-Usage: sign\n";
+ inputData += "Subkey-Type: ECDH\nSubkey-Curve: Curve25519\nSubkey-Usage: encrypt\n";
+ break;
+ default:
+ return null;
+ }
+
+ if (name.replace(/ /g, "").length) {
+ inputData += "Name-Real: " + name + "\n";
+ }
+ if (comment && comment.replace(/ /g, "").length) {
+ inputData += "Name-Comment: " + comment + "\n";
+ }
+ inputData += "Name-Email: " + email + "\n";
+ inputData += "Expire-Date: " + String(expiryDate) + "\n";
+
+ EnigmailLog.CONSOLE(inputData + " \n");
+
+ if (passphrase.length) {
+ inputData += "Passphrase: " + passphrase + "\n";
+ }
+ else {
+ if (EnigmailGpg.getGpgFeature("genkey-no-protection")) {
+ inputData += "%echo no-protection\n";
+ inputData += "%no-protection\n";
+ }
+ }
+
+ inputData += "%commit\n%echo done\n";
+
+ let proc = null;
+
+ try {
+ proc = subprocess.call({
+ command: EnigmailGpg.agentPath,
+ arguments: args,
+ environment: EnigmailCore.getEnvList(),
+ charset: null,
+ stdin: function(pipe) {
+ pipe.write(inputData);
+ pipe.close();
+ },
+ stderr: function(data) {
+ // extract key ID
+ if (data.search(/^\[GNUPG:\] KEY_CREATED/m)) {
+ let m = data.match(/^(\[GNUPG:\] KEY_CREATED [BPS] )([^ \r\n\t]+)$/m);
+ if (m && m.length > 2) {
+ listener.keyId = "0x" + m[2];
+ }
+ }
+ listener.onDataAvailable(data);
+ },
+ done: function(result) {
+ gKeygenProcess = null;
+ try {
+ if (result.exitCode === 0) {
+ EnigmailKeyRing.clearCache();
+ }
+ listener.onStopRequest(result.exitCode);
+ }
+ catch (ex) {}
+ },
+ mergeStderr: false
+ });
+ }
+ catch (ex) {
+ EnigmailLog.ERROR("keyRing.jsm: generateKey: subprocess.call failed with '" + ex.toString() + "'\n");
+ throw ex;
+ }
+
+ gKeygenProcess = proc;
+
+ EnigmailLog.DEBUG("keyRing.jsm: generateKey: subprocess = " + proc + "\n");
+
+ return proc;
+ },
+
+ /**
+ * try to find valid key for encryption to passed email address
+ *
+ * @param details if not null returns error in details.msg
+ *
+ * @return: found key ID (without leading "0x") or null
+ */
+ getValidKeyForRecipient: function(emailAddr, minTrustLevelIndex, details) {
+ EnigmailLog.DEBUG("keyRing.jsm: getValidKeyForRecipient(): emailAddr=\"" + emailAddr + "\"\n");
+ const TRUSTLEVELS_SORTED = EnigmailTrust.trustLevelsSorted();
+ const fullTrustIndex = TRUSTLEVELS_SORTED.indexOf("f");
+
+ emailAddr = emailAddr.toLowerCase();
+ var embeddedEmailAddr = "<" + emailAddr + ">";
+
+ // note: we can't take just the first matched because we might have faked keys as duplicates
+ var foundKeyId = null;
+ var foundTrustLevel = null;
+ var foundKeyTrustIndex = null;
+
+ let k = this.getAllKeys(null, "validity", -1);
+ let keyList = k.keyList;
+ let keySortList = k.keySortList;
+
+ // **** LOOP to check against each key
+ // - note: we have sorted the keys according to validity
+ // to abort the loop as soon as we reach keys that are not valid enough
+ for (var idx = 0; idx < keySortList.length; idx++) {
+ var keyObj = keyList[keySortList[idx].keyNum];
+ var keyTrust = keyObj.keyTrust;
+ var keyTrustIndex = TRUSTLEVELS_SORTED.indexOf(keyTrust);
+ //EnigmailLog.DEBUG("keyRing.jsm: getValidKeyForRecipient(): check key " + keyObj.keyId + "\n");
+
+ // key trust (our sort criterion) too low?
+ // => *** regular END of the loop
+ if (keyTrustIndex < minTrustLevelIndex) {
+ if (!foundKeyId) {
+ if (details) {
+ details.msg = "ProblemNoKey";
+ }
+ let msg = "no key with enough trust level for '" + emailAddr + "' found";
+ EnigmailLog.DEBUG("keyRing.jsm: getValidKeyForRecipient(): " + msg + "\n");
+ }
+ return foundKeyId; // **** regular END OF LOOP (return NULL or found single key)
+ }
+
+ // key valid for encryption?
+ if (keyObj.keyUseFor.indexOf("E") < 0) {
+ //EnigmailLog.DEBUG("keyRing.jsm: getValidKeyForRecipient(): skip key " + keyObj.keyId + " (not provided for encryption)\n");
+ continue; // not valid for encryption => **** CONTINUE the LOOP
+ }
+ // key disabled?
+ if (keyObj.keyUseFor.indexOf("D") >= 0) {
+ //EnigmailLog.DEBUG("keyRing.jsm: getValidKeyForRecipient(): skip key " + keyObj.keyId + " (disabled)\n");
+ continue; // disabled => **** CONTINUE the LOOP
+ }
+
+ // check against the user ID
+ var userId = keyObj.userId.toLowerCase();
+ if (userId && (userId == emailAddr || userId.indexOf(embeddedEmailAddr) >= 0)) {
+ if (keyTrustIndex < minTrustLevelIndex) {
+ EnigmailLog.DEBUG("keyRing.jsm: getValidKeyForRecipient(): matching key=" + keyObj.keyId + " found but not enough trust\n");
+ }
+ else {
+ // key with enough trust level found
+ EnigmailLog.DEBUG("keyRing.jsm: getValidKeyForRecipient(): key=" + keyObj.keyId + " keyTrust=\"" + keyTrust + "\" found\n");
+
+ // immediately return if a fully or ultimately trusted key is found
+ // (faked keys should not be an issue here, so we don't have to check other keys)
+ if (keyTrustIndex >= fullTrustIndex) {
+ return keyObj.keyId;
+ }
+
+ if (foundKeyId != keyObj.keyId) {
+ // new matching key found (note: might find same key via subkeys)
+ if (foundKeyId) {
+ // different matching keys found
+ if (foundKeyTrustIndex > keyTrustIndex) {
+ return foundKeyId; // OK, previously found key has higher trust level
+ }
+ // error because we have two keys with same trust level
+ // => let the user decide (to prevent from using faked keys with default trust level)
+ if (details) {
+ details.msg = "ProblemMultipleKeys";
+ }
+ let msg = "multiple matching keys with same trust level found for '" + emailAddr + "' ";
+ EnigmailLog.DEBUG("keyRing.jsm: getValidKeyForRecipient(): " + msg +
+ " trustLevel=\"" + keyTrust + "\" (0x" + foundKeyId + " and 0x" + keyObj.keyId + ")\n");
+ return null;
+ }
+ // save found key to compare with other matching keys (handling of faked keys)
+ foundKeyId = keyObj.keyId;
+ foundKeyTrustIndex = keyTrustIndex;
+ }
+ continue; // matching key found (again) => **** CONTINUE the LOOP (don't check Sub-UserIDs)
+ }
+ }
+
+ // check against the sub user ID
+ // (if we are here, the primary user ID didn't match)
+ // - Note: sub user IDs have NO owner trust
+ for (var subUidIdx = 1; subUidIdx < keyObj.userIds.length; subUidIdx++) {
+ var subUidObj = keyObj.userIds[subUidIdx];
+ var subUserId = subUidObj.userId.toLowerCase();
+ var subUidTrust = subUidObj.keyTrust;
+ var subUidTrustIndex = TRUSTLEVELS_SORTED.indexOf(subUidTrust);
+ //EnigmailLog.DEBUG("keyRing.jsm: getValidKeyForRecipient(): check subUid " + subUidObj.keyId + "\n");
+
+ if (subUserId && (subUserId == emailAddr || subUserId.indexOf(embeddedEmailAddr) >= 0)) {
+ if (subUidTrustIndex < minTrustLevelIndex) {
+ EnigmailLog.DEBUG("keyRing.jsm: getValidKeyForRecipient(): matching subUid=" + keyObj.keyId + " found but not enough trust\n");
+ }
+ else {
+ // subkey with enough trust level found
+ EnigmailLog.DEBUG("keyRing.jsm: getValidKeyForRecipient(): matching subUid in key=" + keyObj.keyId + " keyTrust=\"" + keyTrust + "\" found\n");
+
+ if (keyTrustIndex >= fullTrustIndex) {
+ // immediately return if a fully or ultimately trusted key is found
+ // (faked keys should not be an issue here, so we don't have to check other keys)
+ return keyObj.keyId;
+ }
+
+ if (foundKeyId != keyObj.keyId) {
+ // new matching key found (note: might find same key via different subkeys)
+ if (foundKeyId) {
+ // different matching keys found
+ if (foundKeyTrustIndex > subUidTrustIndex) {
+ return foundKeyId; // OK, previously found key has higher trust level
+ }
+ // error because we have two keys with same trust level
+ // => let the user decide (to prevent from using faked keys with default trust level)
+ if (details) {
+ details.msg = "ProblemMultipleKeys";
+ }
+ let msg = "multiple matching keys with same trust level found for '" + emailAddr + "' ";
+ EnigmailLog.DEBUG("keyRing.jsm: getValidKeyForRecipient(): " + msg +
+ " trustLevel=\"" + keyTrust + "\" (0x" + foundKeyId + " and 0x" + keyObj.keyId + ")\n");
+ return null;
+ }
+ // save found key to compare with other matching keys (handling of faked keys)
+ foundKeyId = keyObj.keyId;
+ foundKeyTrustIndex = subUidTrustIndex;
+ }
+ }
+ }
+ }
+
+ } // **** LOOP to check against each key
+
+ if (!foundKeyId) {
+ EnigmailLog.DEBUG("keyRing.jsm: getValidKeyForRecipient(): no key for '" + emailAddr + "' found\n");
+ }
+ return foundKeyId;
+ },
+
+ /**
+ * Determine the key ID for a set of given addresses
+ *
+ * @param {Array<String>} addresses: email addresses
+ * @param {String} minTrustLevel: f for Fully trusted keys / ? for any valid key
+ * @param {Object} details: holds details for invalid keys:
+ * - errArray: {
+ * * addr {String}: email addresses
+ * * msg {String}: related error
+ * }
+ * - keyMap {Object<String>}: map of email addr -> keyID
+ * @param {Array<String>} resultingArray: list of found key IDs
+ *
+ * @return {Boolean}: true if at least one key missing; false otherwise
+ */
+ getValidKeysForAllRecipients: function(addresses, minTrustLevel, details, resultingArray) {
+
+ let minTrustLevelIndex = TRUSTLEVELS_SORTED.indexOf(minTrustLevel);
+
+ // check whether each address is or has a key:
+ let keyMissing = false;
+ if (details) {
+ details.errArray = [];
+ details.keyMap = {};
+ }
+ for (let i = 0; i < addresses.length; i++) {
+ let addr = addresses[i];
+ // try to find current address in key list:
+ let keyId = null;
+ var errMsg = null;
+ if (addr.indexOf('@') >= 0) {
+ // try email match:
+ var addrErrDetails = {};
+ let foundKeyId = this.getValidKeyForRecipient(addr, minTrustLevelIndex, addrErrDetails);
+ if (details && addrErrDetails.msg) {
+ errMsg = addrErrDetails.msg;
+ }
+ if (foundKeyId) {
+ keyId = "0x" + foundKeyId.toUpperCase();
+ resultingArray.push(keyId);
+ }
+ }
+ else {
+ // try key match:
+ var keyObj = this.getKeyById(addr);
+
+ if (keyObj) {
+ // if found, check whether the trust level is enough
+ if (TRUSTLEVELS_SORTED.indexOf(keyObj.keyTrust) >= minTrustLevelIndex) {
+ keyId = "0x" + keyObj.keyId.toUpperCase();
+ resultingArray.push(keyId);
+ }
+ }
+ }
+
+ if (keyId) {
+ if (details) {
+ details.keyMap[addr.toLowerCase()] = keyId;
+ }
+ }
+ else {
+ // no key for this address found
+ keyMissing = true;
+ if (details) {
+ if (!errMsg) {
+ errMsg = "ProblemNoKey";
+ }
+ var detailsElem = {};
+ detailsElem.addr = addr;
+ detailsElem.msg = errMsg;
+ details.errArray.push(detailsElem);
+ }
+ EnigmailLog.DEBUG("keyRing.jsm: doValidKeysForAllRecipients(): return null (no single valid key found for=\"" + addr + "\" with minTrustLevel=\"" + minTrustLevel + "\")\n");
+ }
+ }
+ return keyMissing;
+ },
+
+ /**
+ * Rebuild the quick access search indexes after the key list was loaded
+ */
+ rebuildKeyIndex: function() {
+ gKeyIndex = [];
+ gSubkeyIndex = [];
+
+ for (let i in gKeyListObj.keyList) {
+ let k = gKeyListObj.keyList[i];
+ gKeyIndex[k.keyId] = k;
+ gKeyIndex[k.fpr] = k;
+ gKeyIndex[k.keyId.substr(-8, 8)] = k;
+
+ // add subkeys
+ for (let j in k.subKeys) {
+ gSubkeyIndex[k.subKeys[j].keyId] = k;
+ }
+ }
+ },
+
+ /**
+ * Update specific keys in the key cache. If the key objects don't exist yet,
+ * they will be created
+ *
+ * @param keys: Array of String - key IDs or fingerprints
+ */
+ updateKeys: function(keys) {
+ EnigmailLog.DEBUG("keyRing.jsm: updateKeys(" + keys.join(",") + ")\n");
+ let uniqueKeys = [...new Set(keys)]; // make key IDs unique
+
+ deleteKeysFromCache(uniqueKeys);
+
+ if (gKeyListObj.keyList.length > 0) {
+ loadKeyList(null, null, 1, uniqueKeys);
+ }
+ else {
+ loadKeyList(null, null, 1);
+ }
+
+ getWindows().keyManReloadKeys();
+ }
+}; // EnigmailKeyRing
+
+
+/************************ INTERNAL FUNCTIONS ************************/
+
+function sortByUserId(keyListObj, sortDirection) {
+ return function(a, b) {
+ return (a.userId < b.userId) ? -sortDirection : sortDirection;
+ };
+}
+
+const sortFunctions = {
+ keyid: function(keyListObj, sortDirection) {
+ return function(a, b) {
+ return (a.keyId < b.keyId) ? -sortDirection : sortDirection;
+ };
+ },
+
+ keyidshort: function(keyListObj, sortDirection) {
+ return function(a, b) {
+ return (a.keyId.substr(-8, 8) < b.keyId.substr(-8, 8)) ? -sortDirection : sortDirection;
+ };
+ },
+
+ fpr: function(keyListObj, sortDirection) {
+ return function(a, b) {
+ return (keyListObj.keyList[a.keyNum].fpr < keyListObj.keyList[b.keyNum].fpr) ? -sortDirection : sortDirection;
+ };
+ },
+
+ keytype: function(keyListObj, sortDirection) {
+ return function(a, b) {
+ return (keyListObj.keyList[a.keyNum].secretAvailable < keyListObj.keyList[b.keyNum].secretAvailable) ? -sortDirection : sortDirection;
+ };
+ },
+
+ validity: function(keyListObj, sortDirection) {
+ return function(a, b) {
+ return (EnigmailTrust.trustLevelsSorted().indexOf(EnigmailTrust.getTrustCode(keyListObj.keyList[a.keyNum])) < EnigmailTrust.trustLevelsSorted().indexOf(EnigmailTrust.getTrustCode(
+ keyListObj.keyList[b.keyNum]))) ? -sortDirection : sortDirection;
+ };
+ },
+
+ trust: function(keyListObj, sortDirection) {
+ return function(a, b) {
+ return (EnigmailTrust.trustLevelsSorted().indexOf(keyListObj.keyList[a.keyNum].ownerTrust) < EnigmailTrust.trustLevelsSorted().indexOf(keyListObj.keyList[b.keyNum].ownerTrust)) ?
+ -
+ sortDirection : sortDirection;
+ };
+ },
+
+ expiry: function(keyListObj, sortDirection) {
+ return function(a, b) {
+ return (keyListObj.keyList[a.keyNum].expiryTime < keyListObj.keyList[b.keyNum].expiryTime) ? -sortDirection : sortDirection;
+ };
+ }
+};
+
+function getSortFunction(type, keyListObj, sortDirection) {
+ return (sortFunctions[type] || sortByUserId)(keyListObj, sortDirection);
+}
+
+/**
+ * Load the key list into memory and return it sorted by a specified column
+ *
+ * @param win - |object| holding the parent window for displaying error messages
+ * @param sortColumn - |string| containing the column name for sorting. One of:
+ * userid, keyid, keyidshort, fpr, keytype, validity, trust, expiry.
+ * Null will sort by userid.
+ * @param sortDirection - |number| 1 = ascending / -1 = descending
+ * @param onlyKeys - |array| of Strings: if defined, only (re-)load selected key IDs
+ *
+ * no return value
+ */
+function loadKeyList(win, sortColumn, sortDirection, onlyKeys = null) {
+ EnigmailLog.DEBUG("keyRing.jsm: loadKeyList( " + onlyKeys + ")\n");
+
+ if (gLoadingKeys) {
+ waitForKeyList();
+ return;
+ }
+ gLoadingKeys = true;
+
+ try {
+ const cApi = EnigmailCryptoAPI();
+ cApi.getKeys(onlyKeys)
+ .then(keyList => {
+ createAndSortKeyList(keyList, sortColumn, sortDirection, onlyKeys === null);
+ gLoadingKeys = false;
+
+ })
+ .catch(e => {
+ EnigmailLog.ERROR(`keyRing.jsm: loadKeyList: error ${e}
+`);
+ gLoadingKeys = false;
+ });
+ waitForKeyList();
+ }
+ catch (ex) {
+ EnigmailLog.ERROR("keyRing.jsm: loadKeyList: exception: " + ex.toString());
+ }
+}
+
+/**
+ * Update the global key sort-list (quick index to keys)
+ *
+ * no return value
+ */
+function updateSortList() {
+ gKeyListObj.keySortList = [];
+ for (let i = 0; i < gKeyListObj.keyList.length; i++) {
+ let keyObj = gKeyListObj.keyList[i];
+ gKeyListObj.keySortList.push({
+ userId: keyObj.userId.toLowerCase(),
+ keyId: keyObj.keyId,
+ fpr: keyObj.fpr,
+ keyNum: i
+ });
+ }
+
+}
+
+
+/**
+ * Delete a set of keys from the key cache. Does not rebuild key indexes.
+ * Not found keys are skipped.
+ *
+ * @param keyList: Array of Strings: key IDs (or fpr) to delete
+ *
+ * @return Array of deleted key objects
+ */
+
+function deleteKeysFromCache(keyList) {
+ EnigmailLog.DEBUG("keyRing.jsm: deleteKeysFromCache(" + keyList.join(",") + ")\n");
+
+ let deleted = [];
+ let foundKeys = [];
+ for (let keyId of keyList) {
+ let k = EnigmailKeyRing.getKeyById(keyId, true);
+ if (k) {
+ foundKeys.push(k);
+ }
+ }
+
+ for (let k of foundKeys) {
+ let foundIndex = -1;
+ for (let i = 0; i < gKeyListObj.keyList.length; i++) {
+ if (gKeyListObj.keyList[i].fpr == k.fpr) {
+ foundIndex = i;
+ break;
+ }
+ }
+ if (foundIndex >= 0) {
+ gKeyListObj.keyList.splice(foundIndex, 1);
+ deleted.push(k);
+ }
+ }
+
+ return deleted;
+}
+
+function createAndSortKeyList(keyList, sortColumn, sortDirection, resetKeyCache) {
+ EnigmailLog.DEBUG("keyRing.jsm: createAndSortKeyList()\n");
+
+ if (typeof sortColumn !== "string")
+ sortColumn = "userid";
+ if (!sortDirection)
+ sortDirection = 1;
+
+ if ((!("keyList" in gKeyListObj)) || (resetKeyCache)) {
+ gKeyListObj.keyList = [];
+ gKeyListObj.keySortList = [];
+ gKeyListObj.trustModel = "?";
+ }
+
+ gKeyListObj.keyList = gKeyListObj.keyList.concat(keyList.map(k => {
+ return newEnigmailKeyObj(k);
+ }));
+
+ // update the quick index for sorting keys
+ updateSortList();
+
+ // create a hash-index on key ID (8 and 16 characters and fingerprint)
+ // in a single array
+
+ EnigmailKeyRing.rebuildKeyIndex();
+
+ gKeyListObj.keySortList.sort(getSortFunction(sortColumn.toLowerCase(), gKeyListObj, sortDirection));
+}
+
+
+function runKeyUsabilityCheck() {
+ EnigmailLog.DEBUG("keyRing.jsm: runKeyUsabilityCheck()\n");
+
+ EnigmailTimer.setTimeout(function _f() {
+ try {
+ let msg = getKeyUsability().keyExpiryCheck();
+
+ if (msg && msg.length > 0) {
+ getDialog().info(null, msg);
+ }
+ else {
+ getKeyUsability().checkOwnertrust();
+ }
+ }
+ catch (ex) {
+ EnigmailLog.DEBUG("keyRing.jsm: runKeyUsabilityCheck: exception " + ex.message + "\n" + ex.stack + "\n");
+ }
+
+ }, 60 * 1000); // 1 minute
+}
+
+function waitForKeyList() {
+ let mainThread = Services.tm.mainThread;
+ while (gLoadingKeys)
+ mainThread.processNextEvent(true);
+}
+
+
+EnigmailKeyRing.clearCache();
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/keyUsability.jsm
@@ -0,0 +1,329 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailKeyUsability"];
+
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+const EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs.jsm").EnigmailPrefs;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
+const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
+const EnigmailLazy = ChromeUtils.import("chrome://openpgp/content/modules/lazy.jsm").EnigmailLazy;
+
+const getDialog = EnigmailLazy.loader("enigmail/dialog.jsm", "EnigmailDialog");
+const getWindows = EnigmailLazy.loader("enigmail/windows.jsm", "EnigmailWindows");
+const getKeyRing = EnigmailLazy.loader("enigmail/keyRing.jsm", "EnigmailKeyRing");
+
+const DAY = 86400; // number of seconds of 1 day
+
+var EnigmailKeyUsability = {
+ /**
+ * Check whether some key pairs expire in less than N days from now.
+ *
+ * @param keySpecArr - Array: list of key IDs or User IDs
+ * @param numDay - Number: number of days from now
+ *
+ * @return Array - list of keys that will expire
+ */
+
+ getExpiryForKeySpec: function(keySpecArr, numDays) {
+ EnigmailLog.DEBUG("keyUsability.jsm: getExpiryForKeySpec()\n");
+ let now = Math.floor(Date.now() / 1000);
+ let enigmailSvc = EnigmailCore.getService();
+ if (!enigmailSvc) return [];
+
+ let result = keySpecArr.reduce(function(p, keySpec) {
+ let key;
+
+ if (keySpec.search(/^(0x)?[0-9A-F]{8,40}$/i) === 0) {
+ key = getKeyRing().getKeyById(keySpec);
+ }
+ else {
+ key = getKeyRing().getSecretKeyByEmail(keySpec);
+ }
+ if (!key) return p;
+
+ let maxExpiry = Number.MIN_VALUE;
+ let maxKey = null;
+
+ let ex = key.getKeyExpiry();
+ if (ex > maxExpiry) {
+ maxExpiry = ex;
+ maxKey = key;
+ }
+
+ if (maxExpiry < now + (DAY * numDays) && maxExpiry >= now) p.push(maxKey);
+
+ return p;
+ }, []);
+
+ result = uniqueKeyList(result);
+ return result;
+ },
+
+ /**
+ * Determine the configured key specifications for all identities
+ * where Enigmail is enabled
+ *
+ * @return Array of Strings - list of keyId and email addresses
+ */
+ getKeysSpecForIdentities: function() {
+ EnigmailLog.DEBUG("keyUsability.jsm: getKeysSpecForIdentities()\n");
+ let accountManager = Cc["@mozilla.org/messenger/account-manager;1"].getService(Ci.nsIMsgAccountManager);
+
+ let keySpecList = [];
+
+ for (let acct = 0; acct < accountManager.accounts.length; acct++) {
+ let ac = accountManager.accounts.queryElementAt(acct, Ci.nsIMsgAccount);
+
+ for (let i = 0; i < ac.identities.length; i++) {
+ let id = ac.identities.queryElementAt(i, Ci.nsIMsgIdentity);
+ if (id.getBoolAttribute("enablePgp")) {
+ if (id.getIntAttribute("pgpKeyMode") === 1) {
+ keySpecList.push(id.getCharAttribute("pgpkeyId"));
+ }
+ else {
+ keySpecList.push(id.email);
+ }
+ }
+ }
+ }
+
+ return keySpecList;
+ },
+
+ /**
+ * Check if all keys of all configured identities are still valid in N days.
+ * (N is configured via warnKeyExpiryNumDays; 0 = disable the check)
+ *
+ * @return Array of keys - the keys that have expired since the last check
+ * null in case no check was performed
+ */
+ getNewlyExpiredKeys: function() {
+ EnigmailLog.DEBUG("keyUsability.jsm: getNewlyExpiredKeys()\n");
+
+ let numDays = EnigmailPrefs.getPref("warnKeyExpiryNumDays");
+ if (numDays < 1) return null;
+
+ let now = Date.now();
+
+ let lastResult = {
+ expiredList: [],
+ lastCheck: 0
+ };
+
+ let lastRes = EnigmailPrefs.getPref("keyCheckResult");
+ if (lastRes.length > 0) {
+ lastResult = JSON.parse(lastRes);
+ }
+
+ if (now - lastResult.lastCheck < DAY * 1000) return null;
+
+ let keys = this.getKeysSpecForIdentities();
+
+ if (keys.length === 0) {
+ lastResult.lastCheck = now;
+ EnigmailPrefs.setPref("keyCheckResult", JSON.stringify(lastResult));
+ return [];
+ }
+
+ let expired = this.getExpiryForKeySpec(keys, numDays);
+
+ let expiredList = expired.reduce(function _f(p, key) {
+ p.push(key.keyId);
+ return p;
+ }, []);
+
+ let newResult = {
+ expiredList: expiredList,
+ lastCheck: now
+ };
+
+ EnigmailPrefs.setPref("keyCheckResult", JSON.stringify(newResult));
+
+ let warnList = expired.reduce(function _f(p, key) {
+ if (lastResult.expiredList.indexOf(key.keyId) < 0) {
+ p.push(key);
+ }
+ return p;
+ }, []);
+
+ return warnList;
+ },
+
+ keyExpiryCheck: function() {
+ EnigmailLog.DEBUG("keyUsability.jsm: keyExpiryCheck()\n");
+
+ let expiredKeys = this.getNewlyExpiredKeys();
+ if (!expiredKeys || expiredKeys.length === 0) return "";
+
+ let numDays = EnigmailPrefs.getPref("warnKeyExpiryNumDays");
+
+ if (expiredKeys.length === 1) {
+ return EnigmailLocale.getString("expiry.keyExpiresSoon", [getKeyDesc(expiredKeys[0]), numDays]);
+ }
+ else {
+ let keyDesc = "";
+ for (let i = 0; i < expiredKeys.length; i++) {
+ keyDesc += "- " + getKeyDesc(expiredKeys[i]) + "\n";
+ }
+ return EnigmailLocale.getString("expiry.keysExpireSoon", [numDays, keyDesc]);
+ }
+ },
+
+
+ /**
+ * Check whether some key pairs (i.e. key with a secret key) have an
+ * ownertrust of less than "ultimate".
+ *
+ * @param keySpecArr - Array: list of key IDs or User IDs
+ *
+ * @return Array - list of keys that have ownertrust below "ultimate"
+ */
+
+ getOwnerTrustForKeySpec: function(keySpecArr) {
+ EnigmailLog.DEBUG("keyUsability.jsm: getOwnerTrustForKeySpec()\n");
+ let enigmailSvc = EnigmailCore.getService();
+ if (!enigmailSvc) return [];
+
+ let result = keySpecArr.reduce(function(p, keySpec) {
+ let key;
+
+ if (keySpec.search(/^(0x)?[0-9A-F]{8,40}$/i) === 0) {
+ key = getKeyRing().getKeyById(keySpec);
+ if (!key) return p;
+ }
+ else {
+ key = getKeyRing().getSecretKeyByEmail(keySpec);
+ if (!key) return p;
+ }
+
+ let ot = key.ownerTrust;
+ if (ot !== "u") p.push(key);
+
+ return p;
+ }, []);
+
+ result = uniqueKeyList(result);
+ return result;
+ },
+
+
+ /**
+ * Check if all keys of all configured identities have "ultimate" ownertrust
+ *
+ * @return String Message listing the keys that have less ownertrust
+ * resultObj.Count: Number of those keys
+ * resultObj.KeyId: KeyId (only if a single key is concerned)
+ */
+
+ keyOwnerTrustCheck: function(resultObj) {
+ EnigmailLog.DEBUG("keyUsability.jsm: keyOwnerTrustCheck()\n");
+ resultObj.Count = 0;
+
+ let keys = this.getKeysSpecForIdentities();
+
+ if (keys.length === 0) {
+ return "";
+ }
+
+ let keysMissingOwnertrust = this.getOwnerTrustForKeySpec(keys);
+
+ if (!keysMissingOwnertrust || keysMissingOwnertrust.length === 0) return "";
+
+ resultObj.Count = keysMissingOwnertrust.length;
+
+ if (keysMissingOwnertrust.length === 1) {
+ let keyDesc = getKeyDesc(keysMissingOwnertrust[0]);
+ resultObj.keyId = keysMissingOwnertrust[0].keyId;
+ return EnigmailLocale.getString("expiry.keyMissingOwnerTrust", keyDesc);
+ }
+ else {
+ let keyDesc = "";
+ for (let i = 0; i < keysMissingOwnertrust.length; i++) {
+ keyDesc += "- " + getKeyDesc(keysMissingOwnertrust[i]) + "\n";
+ }
+ return EnigmailLocale.getString("expiry.keysMissingOwnerTrust", keyDesc);
+ }
+ },
+
+ /**
+ * Run the check for Ownertrust ("You rely on certifications") and
+ * Display a message if something needs to be done
+ */
+ checkOwnertrust: function() {
+ EnigmailLog.DEBUG("keyUsability.jsm: checkOwnertrust\n");
+
+ var resultObj = {};
+ let msg = this.keyOwnerTrustCheck(resultObj);
+
+ if (msg && (msg.length > 0) && EnigmailPrefs.getPref("warnOnMissingOwnerTrust")) {
+ let actionButtonText = "";
+
+ if (resultObj && resultObj.Count === 1) {
+ // single key is concerned
+ actionButtonText = EnigmailLocale.getString("expiry.OpenKeyProperties");
+ }
+ else {
+ // Multiple keys concerned
+ actionButtonText = EnigmailLocale.getString("expiry.OpenKeyManager");
+ }
+
+ let checkedObj = {};
+ let r = getDialog().msgBox(null, {
+ msgtext: msg,
+ dialogTitle: EnigmailLocale.getString("enigInfo"),
+ checkboxLabel: EnigmailLocale.getString("dlgNoPrompt"),
+ button1: EnigmailLocale.getString("dlg.button.close"),
+ button2: actionButtonText,
+ iconType: EnigmailConstants.ICONTYPE_INFO
+ },
+ checkedObj);
+ if (r >= 0 && checkedObj.value) {
+ // Do not show me this dialog again
+ EnigmailPrefs.setPref("warnOnMissingOwnerTrust", false);
+ }
+ if (r == 1) {
+ if (resultObj && resultObj.Count === 1) {
+ // single key is concerned, open key details dialog
+ getWindows().openKeyDetails(null, resultObj.keyId, false);
+ }
+ else {
+ // Multiple keys concerned, open Key Manager
+ getWindows().openKeyManager(null);
+ }
+ }
+ }
+ }
+};
+
+/**
+ * Remove duplicate key Object elements from an array
+ *
+ * @param arr - Array of key Objects to be worked on
+ *
+ * @return Array - the array without duplicates
+ */
+
+function uniqueKeyList(arr) {
+ return arr.reduce(function(p, c) {
+
+ let r = p.find(function _f(e, i, a) {
+ return e.keyId === c.keyId;
+ });
+
+ if (r === undefined) p.push(c);
+ return p;
+ }, []);
+}
+
+
+function getKeyDesc(key) {
+ return '"' + key.userId + '" (key ID ' + key.fprFormatted + ')';
+}
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/keyserver.jsm
@@ -0,0 +1,1631 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+const EXPORTED_SYMBOLS = ["EnigmailKeyServer"];
+
+Components.utils.importGlobalProperties(["XMLHttpRequest"]);
+const EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs.jsm").EnigmailPrefs;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+const EnigmailKeyRing = ChromeUtils.import("chrome://openpgp/content/modules/keyRing.jsm").EnigmailKeyRing;
+const EnigmailKeyserverURIs = ChromeUtils.import("chrome://openpgp/content/modules/keyserverUris.jsm").EnigmailKeyserverURIs;
+const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
+const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
+const EnigmailExecution = ChromeUtils.import("chrome://openpgp/content/modules/execution.jsm").EnigmailExecution;
+const EnigmailGpg = ChromeUtils.import("chrome://openpgp/content/modules/gpg.jsm").EnigmailGpg;
+const EnigmailHttpProxy = ChromeUtils.import("chrome://openpgp/content/modules/httpProxy.jsm").EnigmailHttpProxy;
+const EnigmailOS = ChromeUtils.import("chrome://openpgp/content/modules/os.jsm").EnigmailOS;
+const EnigmailXhrUtils = ChromeUtils.import("chrome://openpgp/content/modules/xhrUtils.jsm").EnigmailXhrUtils;
+const EnigmailFuncs = ChromeUtils.import("chrome://openpgp/content/modules/funcs.jsm").EnigmailFuncs;
+const EnigmailCryptoAPI = ChromeUtils.import("chrome://openpgp/content/modules/cryptoAPI.jsm").EnigmailCryptoAPI;
+
+const IOSERVICE_CONTRACTID = "@mozilla.org/network/io-service;1";
+
+const ENIG_DEFAULT_HKP_PORT = "11371";
+const ENIG_DEFAULT_HKPS_PORT = "443";
+const ENIG_DEFAULT_LDAP_PORT = "389";
+
+const SKS_CACERT_URL = "https://sks-keyservers.net/sks-keyservers.netCA.pem";
+const HKPS_POOL_HOST = "hkps.pool.sks-keyservers.net";
+const SKS_CACERT_SUBJECTNAME = "CN=sks-keyservers.net CA,O=sks-keyservers.net CA,ST=Oslo,C=NO";
+
+/**
+ KeySrvListener API
+ Object implementing:
+ - onProgress: function(percentComplete) [only implemented for download()]
+ - onCancel: function() - the body will be set by the callee
+*/
+
+
+function createError(errId) {
+ let msg = "";
+ switch (errId) {
+ case EnigmailConstants.KEYSERVER_ERR_ABORTED:
+ msg = EnigmailLocale.getString("keyserver.error.aborted");
+ break;
+ case EnigmailConstants.KEYSERVER_ERR_SERVER_ERROR:
+ msg = EnigmailLocale.getString("keyserver.error.serverError");
+ break;
+ case EnigmailConstants.KEYSERVER_ERR_SERVER_UNAVAILABLE:
+ msg = EnigmailLocale.getString("keyserver.error.unavailable");
+ break;
+ case EnigmailConstants.KEYSERVER_ERR_SECURITY_ERROR:
+ msg = EnigmailLocale.getString("keyserver.error.securityError");
+ break;
+ case EnigmailConstants.KEYSERVER_ERR_CERTIFICATE_ERROR:
+ msg = EnigmailLocale.getString("keyserver.error.certificateError");
+ break;
+ case EnigmailConstants.KEYSERVER_ERR_IMPORT_ERROR:
+ msg = EnigmailLocale.getString("keyserver.error.importError");
+ break;
+ case EnigmailConstants.KEYSERVER_ERR_UNKNOWN:
+ msg = EnigmailLocale.getString("keyserver.error.unknown");
+ break;
+ }
+
+ return {
+ result: errId,
+ errorDetails: msg
+ };
+}
+
+/**
+ * parse a keyserver specification and return host, protocol and port
+ *
+ * @param keyserver: String - name of keyserver with optional protocol and port.
+ * E.g. keys.gnupg.net, hkps://keys.gnupg.net:443
+ *
+ * @return Object: {port, host, protocol} (all Strings)
+ */
+function parseKeyserverUrl(keyserver) {
+ if (keyserver.length > 1024) {
+ // insane length of keyserver is forbidden
+ throw Components.results.NS_ERROR_FAILURE;
+ }
+
+ keyserver = keyserver.toLowerCase().trim();
+ let protocol = "";
+ if (keyserver.search(/^[a-zA-Z0-9_.-]+:\/\//) === 0) {
+ protocol = keyserver.replace(/^([a-zA-Z0-9_.-]+)(:\/\/.*)/, "$1");
+ keyserver = keyserver.replace(/^[a-zA-Z0-9_.-]+:\/\//, "");
+ }
+ else {
+ protocol = "hkp";
+ }
+
+ let port = "";
+ switch (protocol) {
+ case "hkp":
+ port = ENIG_DEFAULT_HKP_PORT;
+ break;
+ case "https":
+ case "hkps":
+ port = ENIG_DEFAULT_HKPS_PORT;
+ break;
+ case "ldap":
+ port = ENIG_DEFAULT_LDAP_PORT;
+ break;
+ }
+
+ let m = keyserver.match(/^(.+)(:)(\d+)$/);
+ if (m && m.length == 4) {
+ keyserver = m[1];
+ port = m[3];
+ }
+
+ if (keyserver.search(/^(keys\.mailvelope\.com|api\.protonmail\.ch)$/) === 0) {
+ protocol = "hkps";
+ port = ENIG_DEFAULT_HKPS_PORT;
+ }
+ if (keyserver.search(/^(keybase\.io)$/) === 0) {
+ protocol = "keybase";
+ port = ENIG_DEFAULT_HKPS_PORT;
+ }
+
+ return {
+ protocol: protocol,
+ host: keyserver,
+ port: port
+ };
+}
+
+
+/**
+ Object to handle HKP/HKPS requests via builtin XMLHttpRequest()
+ */
+const accessHkpInternal = {
+ /**
+ * Create the payload of hkp requests (upload only)
+ *
+ */
+ buildHkpPayload: function(actionFlag, searchTerms) {
+ let payLoad = null,
+ keyData = "";
+
+ switch (actionFlag) {
+ case EnigmailConstants.UPLOAD_KEY:
+ keyData = EnigmailKeyRing.extractKey(false, searchTerms, null, {}, {});
+ if (keyData.length === 0) return null;
+
+ payLoad = "keytext=" + encodeURIComponent(keyData);
+ return payLoad;
+
+ case EnigmailConstants.DOWNLOAD_KEY:
+ case EnigmailConstants.SEARCH_KEY:
+ case EnigmailConstants.GET_SKS_CACERT:
+ return "";
+ }
+
+ // other actions are not yet implemented
+ return null;
+ },
+
+ /**
+ * return the URL and the HTTP access method for a given action
+ */
+ createRequestUrl: function(keyserver, actionFlag, searchTerm) {
+ let keySrv = parseKeyserverUrl(keyserver);
+
+ let method = "GET";
+ let protocol;
+
+ switch (keySrv.protocol) {
+ case "hkp":
+ protocol = "http";
+ break;
+ case "ldap":
+ throw Components.results.NS_ERROR_FAILURE;
+ default: // equals to hkps
+ protocol = "https";
+ }
+
+ let url = protocol + "://" + keySrv.host + ":" + keySrv.port;
+
+ if (actionFlag === EnigmailConstants.UPLOAD_KEY) {
+ url += "/pks/add";
+ method = "POST";
+ }
+ else if (actionFlag === EnigmailConstants.DOWNLOAD_KEY) {
+ if (searchTerm.indexOf("0x") !== 0) {
+ searchTerm = "0x" + searchTerm;
+ }
+ url += "/pks/lookup?search=" + searchTerm + "&op=get&options=mr";
+ }
+ else if (actionFlag === EnigmailConstants.SEARCH_KEY) {
+ url += "/pks/lookup?search=" + escape(searchTerm) + "&fingerprint=on&op=index&options=mr";
+ }
+ else if (actionFlag === EnigmailConstants.GET_SKS_CACERT) {
+ url = SKS_CACERT_URL;
+ }
+
+ return {
+ url: url,
+ host: keySrv.host,
+ method: method
+ };
+ },
+
+ /**
+ * Upload, search or download keys from a keyserver
+ * @param actionFlag: Number - Keyserver Action Flags: from EnigmailConstants
+ * @param keyId: String - space-separated list of search terms or key IDs
+ * @param keyserver: String - keyserver URL (optionally incl. protocol)
+ * @param listener: optional Object implementing the KeySrvListener API (above)
+ *
+ * @return: Promise<Number (Status-ID)>
+ */
+ accessKeyServer: function(actionFlag, keyserver, keyId, listener) {
+ EnigmailLog.DEBUG(`keyserver.jsm: accessHkpInternal.accessKeyServer(${keyserver})\n`);
+ if (keyserver === null) {
+ keyserver = EnigmailKeyserverURIs.getDefaultKeyServer();
+ }
+
+ return new Promise((resolve, reject) => {
+ let xmlReq = null;
+ if (listener && typeof(listener) === "object") {
+ listener.onCancel = function() {
+ EnigmailLog.DEBUG(`keyserver.jsm: accessHkpInternal.accessKeyServer - onCancel() called\n`);
+ if (xmlReq) {
+ xmlReq.abort();
+ }
+ reject(createError(EnigmailConstants.KEYSERVER_ERR_ABORTED));
+ };
+ }
+ if (actionFlag === EnigmailConstants.REFRESH_KEY) {
+ // we don't (need to) distinguish between refresh and download for our internal protocol
+ actionFlag = EnigmailConstants.DOWNLOAD_KEY;
+ }
+
+ let payLoad = this.buildHkpPayload(actionFlag, keyId);
+ if (payLoad === null) {
+ reject(createError(EnigmailConstants.KEYSERVER_ERR_UNKNOWN));
+ return;
+ }
+
+ let errorCode = 0;
+
+ xmlReq = new XMLHttpRequest();
+
+ xmlReq.onload = function _onLoad() {
+ EnigmailLog.DEBUG("keyserver.jsm: accessHkpInternal: onload(): status=" + xmlReq.status + "\n");
+ switch (actionFlag) {
+ case EnigmailConstants.UPLOAD_KEY:
+ EnigmailLog.DEBUG("keyserver.jsm: accessHkpInternal: onload: " + xmlReq.responseText + "\n");
+ if (xmlReq.status >= 400) {
+ reject(createError(EnigmailConstants.KEYSERVER_ERR_SERVER_ERROR));
+ }
+ else {
+ resolve(0);
+ }
+ return;
+
+ case EnigmailConstants.SEARCH_KEY:
+ case EnigmailConstants.GET_SKS_CACERT:
+ if (xmlReq.status === 404) {
+ // key not found
+ resolve("");
+ }
+ else if (xmlReq.status >= 400) {
+ reject(createError(EnigmailConstants.KEYSERVER_ERR_SERVER_ERROR));
+ }
+ else {
+ resolve(xmlReq.responseText);
+ }
+ return;
+
+ case EnigmailConstants.DOWNLOAD_KEY:
+ if (xmlReq.status >= 400 && xmlReq.status < 500) {
+ // key not found
+ resolve(1);
+ }
+ else if (xmlReq.status >= 500) {
+ EnigmailLog.DEBUG("keyserver.jsm: accessHkpInternal: onload: " + xmlReq.responseText + "\n");
+ reject(createError(EnigmailConstants.KEYSERVER_ERR_SERVER_ERROR));
+ }
+ else {
+ let errorMsgObj = {},
+ importedKeysObj = {};
+ let importMinimal = (xmlReq.responseText.length > 1024000 && (!EnigmailGpg.getGpgFeature("handles-huge-keys")));
+ let r = EnigmailKeyRing.importKey(null, false, xmlReq.responseText, "", errorMsgObj, importedKeysObj, importMinimal);
+ if (r === 0) {
+ resolve(importedKeysObj.value);
+ }
+ else {
+ reject(createError(EnigmailConstants.KEYSERVER_ERR_IMPORT_ERROR));
+ }
+ }
+ return;
+ }
+ resolve(-1);
+ };
+
+ xmlReq.onerror = function(e) {
+ EnigmailLog.DEBUG("keyserver.jsm: accessHkpInternal.accessKeyServer: onerror: " + e + "\n");
+ let err = EnigmailXhrUtils.createTCPErrorFromFailedXHR(e.target);
+ switch (err.type) {
+ case 'SecurityCertificate':
+ reject(createError(EnigmailConstants.KEYSERVER_ERR_CERTIFICATE_ERROR));
+ break;
+ case 'SecurityProtocol':
+ reject(createError(EnigmailConstants.KEYSERVER_ERR_SECURITY_ERROR));
+ break;
+ case 'Network':
+ reject(createError(EnigmailConstants.KEYSERVER_ERR_SERVER_UNAVAILABLE));
+ break;
+ }
+ reject(createError(EnigmailConstants.KEYSERVER_ERR_SERVER_UNAVAILABLE));
+ };
+
+ xmlReq.onloadend = function() {
+ EnigmailLog.DEBUG("keyserver.jsm: accessHkpInternal.accessKeyServer: loadEnd\n");
+ };
+
+ let {
+ url,
+ host,
+ method
+ } = this.createRequestUrl(keyserver, actionFlag, keyId);
+
+ if (host === HKPS_POOL_HOST && actionFlag !== EnigmailConstants.GET_SKS_CACERT) {
+ this.getSksCACert().then(r => {
+ EnigmailLog.DEBUG(`keyserver.jsm: accessHkpInternal.accessKeyServer: getting ${url}\n`);
+ xmlReq.open(method, url);
+ xmlReq.send(payLoad);
+ });
+ }
+ else {
+ EnigmailLog.DEBUG(`keyserver.jsm: accessHkpInternal.accessKeyServer: requesting ${url}\n`);
+ xmlReq.open(method, url);
+ xmlReq.send(payLoad);
+ }
+ });
+ },
+
+ installSksCACert: async function() {
+ EnigmailLog.DEBUG(`keyserver.jsm: installSksCACert()\n`);
+ let certDb = Cc["@mozilla.org/security/x509certdb;1"].getService(Ci.nsIX509CertDB);
+ try {
+ let certTxt = await this.accessKeyServer(EnigmailConstants.GET_SKS_CACERT, "", "", null);
+ const BEGIN_CERT = "-----BEGIN CERTIFICATE-----";
+ const END_CERT = "-----END CERTIFICATE-----";
+
+ certTxt = certTxt.replace(/[\r\n]/g, "");
+ let begin = certTxt.indexOf(BEGIN_CERT);
+ let end = certTxt.indexOf(END_CERT);
+ let certData = certTxt.substring(begin + BEGIN_CERT.length, end);
+ let x509cert = certDb.addCertFromBase64(certData, "C,C,C", "");
+ return x509cert;
+ }
+ catch (x) {
+ return null;
+ }
+ },
+
+ /**
+ * Get the CA certificate for the HKPS sks-keyserver pool
+ */
+ getSksCACert: async function() {
+ EnigmailLog.DEBUG(`keyserver.jsm: getSksCACert()\n`);
+ let certDb = Cc["@mozilla.org/security/x509certdb;1"].getService(Ci.nsIX509CertDB);
+ let cert = null;
+
+ let it = certDb.getCerts().getEnumerator();
+ while (it.hasMoreElements()) {
+ cert = it.getNext().QueryInterface(Ci.nsIX509Cert);
+ if (cert.subjectName === SKS_CACERT_SUBJECTNAME && cert.certType === Ci.nsIX509Cert.CA_CERT) {
+ return cert;
+ }
+ }
+
+ cert = await this.installSksCACert();
+ return cert;
+ },
+
+ /**
+ * Download keys from a keyserver
+ * @param keyIDs: String - space-separated list of search terms or key IDs
+ * @param keyserver: String - keyserver URL (optionally incl. protocol)
+ * @param listener: optional Object implementing the KeySrvListener API (above)
+ *
+ * @return: Promise<...>
+ */
+ download: async function(keyIDs, keyserver, listener = null) {
+ EnigmailLog.DEBUG(`keyserver.jsm: accessHkpInternal.download(${keyIDs})\n`);
+ let keyIdArr = keyIDs.split(/ +/);
+ let retObj = {
+ result: 0,
+ errorDetails: "",
+ keyList: []
+ };
+
+ for (let i = 0; i < keyIdArr.length; i++) {
+ try {
+ let r = await this.accessKeyServer(EnigmailConstants.DOWNLOAD_KEY, keyserver, keyIdArr[i], listener);
+ if (Array.isArray(r)) {
+ retObj.keyList = retObj.keyList.concat(r);
+ }
+ }
+ catch (ex) {
+ retObj.result = ex.result;
+ retObj.errorDetails = ex.errorDetails;
+ throw retObj;
+ }
+
+ if (listener && "onProgress" in listener) {
+ listener.onProgress((i + 1) / keyIdArr.length * 100);
+ }
+ }
+
+ return retObj;
+ },
+
+ refresh: function(keyServer, listener = null) {
+ let keyList = EnigmailKeyRing.getAllKeys().keyList.map(keyObj => {
+ return "0x" + keyObj.fpr;
+ }).join(" ");
+
+ return this.download(keyList, keyServer, listener);
+ },
+
+ /**
+ * Upload keys to a keyserver
+ * @param keyIDs: String - space-separated list of search terms or key IDs
+ * @param keyserver: String - keyserver URL (optionally incl. protocol)
+ * @param listener: optional Object implementing the KeySrvListener API (above)
+ *
+ * @return: Promise<...>
+ */
+ upload: async function(keyIDs, keyserver, listener = null) {
+ EnigmailLog.DEBUG(`keyserver.jsm: accessHkpInternal.upload(${keyIDs})\n`);
+ let keyIdArr = keyIDs.split(/ +/);
+ let retObj = {
+ result: 0,
+ errorDetails: "",
+ keyList: []
+ };
+
+ for (let i = 0; i < keyIdArr.length; i++) {
+ try {
+ let r = await this.accessKeyServer(EnigmailConstants.UPLOAD_KEY, keyserver, keyIdArr[i], listener);
+ if (r === 0) {
+ retObj.keyList.push(keyIdArr[i]);
+ }
+ else {
+ retObj.result = r;
+ }
+ }
+ catch (ex) {
+ retObj.result = ex.result;
+ retObj.errorDetails = ex.errorDetails;
+ throw retObj;
+ }
+
+ if (listener && "onProgress" in listener) {
+ listener.onProgress((i + 1) / keyIdArr.length * 100);
+ }
+ }
+
+ return retObj;
+ },
+
+ /**
+ * Search for keys on a keyserver
+ * @param searchTerm: String - search term
+ * @param keyserver: String - keyserver URL (optionally incl. protocol)
+ * @param listener: optional Object implementing the KeySrvListener API (above)
+ *
+ * @return: Promise<Object>
+ * - result: Number
+ * - pubKeys: Array of Object:
+ * PubKeys: Object with:
+ * - keyId: String
+ * - keyLen: String
+ * - keyType: String
+ * - created: String (YYYY-MM-DD)
+ * - status: String: one of ''=valid, r=revoked, e=expired
+ * - uid: Array of Strings with UIDs
+ */
+ search: async function(searchTerm, keyserver, listener = null) {
+ EnigmailLog.DEBUG(`keyserver.jsm: accessHkpInternal.search(${searchTerm})\n`);
+ let retObj = {
+ result: 0,
+ errorDetails: "",
+ pubKeys: []
+ };
+ let key = null;
+
+ let searchArr = searchTerm.split(/ +/);
+
+ try {
+ for (let k in searchArr) {
+ let r = await this.accessKeyServer(EnigmailConstants.SEARCH_KEY, keyserver, searchArr[k], listener);
+
+ let lines = r.split(/\r?\n/);
+
+ for (var i = 0; i < lines.length; i++) {
+ let line = lines[i].split(/:/).map(unescape);
+ if (line.length <= 1) continue;
+
+ switch (line[0]) {
+ case "info":
+ if (line[1] !== "1") {
+ // protocol version not supported
+ throw {
+ result: 7,
+ errorDetails: EnigmailLocale.getString("keyserver.error.unsupported"),
+ pubKeys: []
+ };
+ }
+ break;
+ case "pub":
+ if (line.length >= 6) {
+ if (key) {
+ retObj.pubKeys.push(key);
+ key = null;
+ }
+ let dat = new Date(line[4] * 1000);
+ let month = String(dat.getMonth() + 101).substr(1);
+ let day = String(dat.getDate() + 100).substr(1);
+ key = {
+ keyId: line[1],
+ keyLen: line[3],
+ keyType: line[2],
+ created: dat.getFullYear() + "-" + month + "-" + day,
+ uid: [],
+ status: line[6]
+ };
+ }
+ break;
+ case "uid":
+ key.uid.push(EnigmailData.convertToUnicode(line[1].trim(), "utf-8"));
+ }
+ }
+
+ if (key) {
+ retObj.pubKeys.push(key);
+ }
+ }
+ }
+ catch (ex) {
+ retObj.result = ex.result;
+ retObj.errorDetails = ex.errorDetails;
+ throw retObj;
+ }
+
+ return retObj;
+ }
+};
+
+/**
+ Object to handle KeyBase requests (search & download only)
+ */
+const accessKeyBase = {
+ /**
+ * return the URL and the HTTP access method for a given action
+ */
+ createRequestUrl: function(actionFlag, searchTerm) {
+ const method = "GET";
+
+ let url = "https://keybase.io/_/api/1.0/user/";
+
+ if (actionFlag === EnigmailConstants.UPLOAD_KEY) {
+ // not supported
+ throw Components.results.NS_ERROR_FAILURE;
+ }
+ else if (actionFlag === EnigmailConstants.DOWNLOAD_KEY) {
+ if (searchTerm.indexOf("0x") === 0) {
+ searchTerm = searchTerm.substr(0, 40);
+ }
+ url += "lookup.json?key_fingerprint=" + escape(searchTerm) + "&fields=public_keys";
+ }
+ else if (actionFlag === EnigmailConstants.SEARCH_KEY) {
+ url += "autocomplete.json?q=" + escape(searchTerm);
+ }
+
+ return {
+ url: url,
+ method: "GET"
+ };
+ },
+
+ /**
+ * Upload, search or download keys from a keyserver
+ * @param actionFlag: Number - Keyserver Action Flags: from EnigmailConstants
+ * @param keyId: String - space-separated list of search terms or key IDs
+ * @param listener: optional Object implementing the KeySrvListener API (above)
+ *
+ * @return: Promise<Number (Status-ID)>
+ */
+ accessKeyServer: function(actionFlag, keyId, listener) {
+ EnigmailLog.DEBUG(`keyserver.jsm: accessKeyBase: accessKeyServer()\n`);
+
+ return new Promise((resolve, reject) => {
+ let xmlReq = null;
+ if (listener && typeof(listener) === "object") {
+ listener.onCancel = function() {
+ EnigmailLog.DEBUG(`keyserver.jsm: accessKeyBase: accessKeyServer - onCancel() called\n`);
+ if (xmlReq) {
+ xmlReq.abort();
+ }
+ reject(createError(EnigmailConstants.KEYSERVER_ERR_ABORTED));
+ };
+ }
+ if (actionFlag === EnigmailConstants.REFRESH_KEY) {
+ // we don't (need to) distinguish between refresh and download for our internal protocol
+ actionFlag = EnigmailConstants.DOWNLOAD_KEY;
+ }
+
+ let errorCode = 0;
+
+ xmlReq = new XMLHttpRequest();
+
+ xmlReq.onload = function _onLoad() {
+ EnigmailLog.DEBUG("keyserver.jsm: onload(): status=" + xmlReq.status + "\n");
+ switch (actionFlag) {
+ case EnigmailConstants.SEARCH_KEY:
+ if (xmlReq.status >= 400) {
+ reject(createError(EnigmailConstants.KEYSERVER_ERR_SERVER_ERROR));
+ }
+ else {
+ resolve(xmlReq.responseText);
+ }
+ return;
+
+ case EnigmailConstants.DOWNLOAD_KEY:
+ if (xmlReq.status >= 400 && xmlReq.status < 500) {
+ // key not found
+ resolve([]);
+ }
+ else if (xmlReq.status >= 500) {
+ EnigmailLog.DEBUG("keyserver.jsm: onload: " + xmlReq.responseText + "\n");
+ reject(createError(EnigmailConstants.KEYSERVER_ERR_SERVER_ERROR));
+ }
+ else {
+ try {
+ let resp = JSON.parse(xmlReq.responseText);
+ let imported = [];
+
+ if (resp.status.code === 0) {
+ for (let hit in resp.them) {
+ EnigmailLog.DEBUG(JSON.stringify(resp.them[hit].public_keys.primary) + "\n");
+
+ if (resp.them[hit] !== null) {
+ let errorMsgObj = {},
+ importedKeysObj = {};
+ let r = EnigmailKeyRing.importKey(null, false, resp.them[hit].public_keys.primary.bundle, "", errorMsgObj, importedKeysObj);
+ if (r === 0) {
+ imported.push(importedKeysObj.value);
+ }
+ }
+ }
+ }
+ resolve(imported);
+ }
+ catch (ex) {
+ reject(createError(EnigmailConstants.KEYSERVER_ERR_UNKNOWN));
+ }
+ }
+ return;
+ }
+ resolve(-1);
+ };
+
+ xmlReq.onerror = function(e) {
+ EnigmailLog.DEBUG("keyserver.jsm: accessKeyBase: onerror: " + e + "\n");
+ let err = EnigmailXhrUtils.createTCPErrorFromFailedXHR(e.target);
+ switch (err.type) {
+ case 'SecurityCertificate':
+ reject(createError(EnigmailConstants.KEYSERVER_ERR_CERTIFICATE_ERROR));
+ break;
+ case 'SecurityProtocol':
+ reject(createError(EnigmailConstants.KEYSERVER_ERR_SECURITY_ERROR));
+ break;
+ case 'Network':
+ reject(createError(EnigmailConstants.KEYSERVER_ERR_SERVER_UNAVAILABLE));
+ break;
+ }
+ reject(createError(EnigmailConstants.KEYSERVER_ERR_SERVER_UNAVAILABLE));
+ };
+
+ xmlReq.onloadend = function() {
+ EnigmailLog.DEBUG("keyserver.jsm: accessKeyBase: loadEnd\n");
+ };
+
+ let {
+ url,
+ method
+ } = this.createRequestUrl(actionFlag, keyId);
+
+ EnigmailLog.DEBUG(`keyserver.jsm: accessKeyBase: requesting ${url}\n`);
+ xmlReq.open(method, url);
+ xmlReq.send("");
+ });
+ },
+
+ /**
+ * Download keys from a KeyBase
+ * @param keyIDs: String - space-separated list of search terms or key IDs
+ * @param keyserver: (not used for keybase)
+ * @param listener: optional Object implementing the KeySrvListener API (above)
+ *
+ * @return: Promise<...>
+ */
+ download: async function(keyIDs, keyserver, listener = null) {
+ EnigmailLog.DEBUG(`keyserver.jsm: accessKeyBase: download()\n`);
+ let keyIdArr = keyIDs.split(/ +/);
+ let retObj = {
+ result: 0,
+ errorDetails: "",
+ keyList: []
+ };
+
+
+ for (let i = 0; i < keyIdArr.length; i++) {
+ try {
+ let r = await this.accessKeyServer(EnigmailConstants.DOWNLOAD_KEY, keyIdArr[i], listener);
+ if (r.length > 0) {
+ retObj.keyList = retObj.keyList.concat(r);
+ }
+ }
+ catch (ex) {
+ retObj.result = ex.result;
+ retObj.errorDetails = ex.result;
+ throw retObj;
+ }
+
+ if (listener && "onProgress" in listener) {
+ listener.onProgress(i / keyIdArr.length);
+ }
+ }
+
+ return retObj;
+ },
+
+ /**
+ * Search for keys on a keyserver
+ * @param searchTerm: String - search term
+ * @param keyserver: String - keyserver URL (optionally incl. protocol)
+ * @param listener: optional Object implementing the KeySrvListener API (above)
+ *
+ * @return: Promise<Object>
+ * - result: Number
+ * - pubKeys: Array of Object:
+ * PubKeys: Object with:
+ * - keyId: String
+ * - keyLen: String
+ * - keyType: String
+ * - created: String (YYYY-MM-DD)
+ * - status: String: one of ''=valid, r=revoked, e=expired
+ * - uid: Array of Strings with UIDs
+
+ */
+ search: async function(searchTerm, keyserver, listener = null) {
+ EnigmailLog.DEBUG(`keyserver.jsm: accessKeyBase: search()\n`);
+ let retObj = {
+ result: 0,
+ errorDetails: "",
+ pubKeys: []
+ };
+
+ let key = {};
+
+ try {
+ let r = await this.accessKeyServer(EnigmailConstants.SEARCH_KEY, searchTerm, listener);
+
+ let res = JSON.parse(r);
+ let completions = res.completions;
+
+ for (let hit in completions) {
+ if (completions[hit] && completions[hit].components.key_fingerprint !== undefined) {
+ let uid = completions[hit].components.username.val;
+ if ("full_name" in completions[hit].components) {
+ uid += " (" + completions[hit].components.full_name.val + ")";
+ }
+ let key = {
+ keyId: completions[hit].components.key_fingerprint.val.toUpperCase(),
+ keyLen: completions[hit].components.key_fingerprint.nbits.toString(),
+ keyType: completions[hit].components.key_fingerprint.algo.toString(),
+ created: 0, //date.toDateString(),
+ uid: [uid],
+ status: ""
+ };
+ retObj.pubKeys.push(key);
+ }
+ }
+ }
+ catch (ex) {
+ retObj.result = ex.result;
+ retObj.errorDetails = ex.errorDetails;
+ throw retObj;
+ }
+
+ return retObj;
+ },
+
+ upload: function() {
+ throw Components.results.NS_ERROR_FAILURE;
+ },
+
+ refresh: function(keyServer, listener = null) {
+ EnigmailLog.DEBUG(`keyserver.jsm: accessKeyBase: refresh()\n`);
+ let keyList = EnigmailKeyRing.getAllKeys().keyList.map(keyObj => {
+ return "0x" + keyObj.fpr;
+ }).join(" ");
+
+ return this.download(keyList, keyServer, listener);
+ }
+};
+
+
+/**
+ Object to handle HKP/HKPS and LDAP/LDAPS requests via GnuPG
+ */
+const accessGnuPG = {
+
+ /**
+ * Upload, search or download keys from a keyserver
+ * @param actionFlag: Number - Keyserver Action Flags: from EnigmailConstants
+ * @param keyId: String - space-separated list of search terms or key IDs
+ * @param keyserver: String - keyserver URL (optionally incl. protocol)
+ * @param listener: optional Object implementing the KeySrvListener API (above)
+ *
+ * @return Promise<Object> Object from execAsync
+ */
+ accessKeyServer: function(actionFlag, keyserver, keyId, listener) {
+ EnigmailLog.DEBUG(`keyserver.jsm: accessGnuPG: accessKeyServer(${keyserver})\n`);
+
+ let processHandle = {
+ value: null
+ };
+
+ if (listener) {
+ listener._isCancelled = 0;
+ listener.onCancel = function() {
+ EnigmailLog.DEBUG(`keyserver.jsm: accessGnuPG: accessKeyServer: onCancel\n`);
+
+ if (processHandle.value) {
+ processHandle.value.killProcess();
+ }
+ this._isCancelled = 1;
+ };
+ }
+
+ if (keyserver === null) {
+ keyserver = EnigmailKeyserverURIs.getDefaultKeyServer();
+ }
+
+ let args = EnigmailGpg.getStandardArgs(true);
+ args.push("--log-file");
+ args.push(EnigmailOS.isWin32 ? "NUL" : "/dev/null");
+ args.push("--with-colons");
+
+ let cmd = "";
+
+ let proxyHost = EnigmailHttpProxy.getHttpProxy();
+ if (proxyHost) {
+ args = args.concat(["--keyserver-options", "http-proxy=" + proxyHost]);
+ }
+
+ args.push("--keyserver");
+ args.push(keyserver);
+
+ switch (actionFlag) {
+ case EnigmailConstants.SEARCH_KEY:
+ cmd = "--search-keys";
+ break;
+ case EnigmailConstants.DOWNLOAD_KEY:
+ cmd = "--recv-keys";
+ break;
+ case EnigmailConstants.UPLOAD_KEY:
+ cmd = "--send-keys";
+ break;
+ }
+
+ args.push(cmd);
+ args = args.concat(keyId.split(/ +/));
+
+ return EnigmailExecution.execAsync(EnigmailGpg.agentPath, args, "", processHandle);
+ },
+
+ parseStatusMsg: function(execResult) {
+ let errorCode = 0,
+ errorType = null;
+
+ // Find the 1st FAILURE message in the gpg status output
+ let m = execResult.stderrData.match(/^\[GNUPG:\] (FAILURE|ERROR) ([^ ]+ )(\d+)/m);
+
+ if (m && m.length >= 4 && m[3].search(/^[0-9]+$/) === 0) {
+ let errorNumber = Number(m[3]);
+ //let sourceSystem = errorNumber >> 24;
+ errorCode = errorNumber & 0xFFFFFF;
+
+ switch (errorCode) {
+ case 58: // no data
+ break;
+ case 32793: // connection refused
+ case 32810: // host unreachable
+ case 220: // Server not found (no name)
+ errorType = EnigmailConstants.KEYSERVER_ERR_SERVER_UNAVAILABLE;
+ break;
+ case 228:
+ errorType = EnigmailConstants.KEYSERVER_ERR_SECURITY_ERROR;
+ break;
+ case 100: // various certificate errors
+ case 101:
+ case 102:
+ case 103:
+ case 185:
+ errorType = EnigmailConstants.KEYSERVER_ERR_CERTIFICATE_ERROR;
+ break;
+ default:
+ errorType = EnigmailConstants.KEYSERVER_ERR_SERVER_ERROR;
+ }
+ }
+
+ if (execResult.isKilled !== 0) {
+ errorType = EnigmailConstants.KEYSERVER_ERR_ABORTED;
+ }
+
+ if (errorType !== null) {
+ EnigmailLog.DEBUG(`keyserver.jsm: accessGnuPG.parseStatusMsg: got errorCode=${errorCode}\n`);
+ return createError(errorType);
+ }
+
+ return null;
+ },
+ /**
+ * Download keys from a keyserver
+ * @param keyIDs: String - space-separated list of search terms or key IDs
+ * @param keyserver: String - keyserver URL (optionally incl. protocol)
+ * @param listener: optional Object implementing the KeySrvListener API (above)
+ *
+ * @return: Promise<...>
+ */
+ download: async function(keyIDs, keyserver, listener = null) {
+ EnigmailLog.DEBUG(`keyserver.jsm: accessGnuPG.download(${keyIDs})\n`);
+ let retObj = {
+ result: 0,
+ errorDetails: "",
+ keyList: []
+ };
+ let keyIdArr = keyIDs.split(/ +/);
+
+ for (let i = 0; i < keyIdArr.length; i++) {
+ let r = await this.accessKeyServer(EnigmailConstants.DOWNLOAD_KEY, keyserver, keyIdArr[i], listener);
+
+ let exitValue = this.parseStatusMsg(r);
+ if (exitValue) {
+ exitValue.keyList = [];
+ throw exitValue;
+ }
+
+ var statusLines = r.statusMsg.split(/\r?\n/);
+
+ for (let j = 0; j < statusLines.length; j++) {
+ let matches = statusLines[j].match(/IMPORT_OK ([0-9]+) (\w+)/);
+ if (matches && (matches.length > 2)) {
+ retObj.keyList.push(matches[2]);
+ EnigmailKeyRing.updateKeys([matches[2]]);
+ }
+ }
+
+ if (listener && "onProgress" in listener) {
+ listener.onProgress((i + 1) / keyIdArr.length * 100);
+ }
+ }
+
+ return retObj;
+ },
+
+ refresh: function(keyServer, listener = null) {
+ let keyList = EnigmailKeyRing.getAllKeys().keyList.map(keyObj => {
+ return "0x" + keyObj.fpr;
+ }).join(" ");
+
+ return this.download(keyList, keyServer, listener);
+ },
+
+ /**
+ * Upload keys to a keyserver
+ * @param keyIDs: String - space-separated list of search terms or key IDs
+ * @param keyserver: String - keyserver URL (optionally incl. protocol)
+ * @param listener: optional Object implementing the KeySrvListener API (above)
+ *
+ * @return: Promise<...>
+ */
+ upload: async function(keyIDs, keyserver, listener = null) {
+ EnigmailLog.DEBUG(`keyserver.jsm: accessGnuPG.upload(${keyIDs})\n`);
+ let keyIdArr = keyIDs.split(/ +/);
+ let retObj = {
+ result: 0,
+ errorDetails: "",
+ keyList: []
+ };
+
+ for (let i = 0; i < keyIdArr.length; i++) {
+ let r = await this.accessKeyServer(EnigmailConstants.UPLOAD_KEY, keyserver, keyIdArr[i], listener);
+
+ let exitValue = this.parseStatusMsg(r);
+ if (exitValue) {
+ exitValue.keyList = [];
+ throw exitValue;
+ }
+
+ if (r.exitCode === 0) {
+ retObj.keyList.push(keyIdArr[i]);
+ }
+
+ if (listener && "onProgress" in listener) {
+ listener.onProgress((i + 1) / keyIdArr.length * 100);
+ }
+ }
+
+ return retObj;
+ },
+
+ /**
+ * Search for keys on a keyserver
+ * @param searchTerm: String - search term
+ * @param keyserver: String - keyserver URL (optionally incl. protocol)
+ * @param listener: optional Object implementing the KeySrvListener API (above)
+ *
+ * @return: Promise<Object>
+ * - result: Number
+ * - pubKeys: Array of Object:
+ * PubKeys: Object with:
+ * - keyId: String
+ * - keyLen: String
+ * - keyType: String
+ * - created: String (YYYY-MM-DD)
+ * - status: String: one of ''=valid, r=revoked, e=expired
+ * - uid: Array of Strings with UIDs
+ */
+ search: async function(searchTerm, keyserver, listener = null) {
+ EnigmailLog.DEBUG(`keyserver.jsm: accessGnuPG.search(${searchTerm})\n`);
+ let retObj = {
+ result: 0,
+ errorDetails: "",
+ pubKeys: []
+ };
+ let key = null;
+
+ try {
+ let r = await this.accessKeyServer(EnigmailConstants.SEARCH_KEY, keyserver, searchTerm, listener);
+
+ let exitValue = this.parseStatusMsg(r);
+ if (exitValue) {
+ exitValue.pubKeys = [];
+ throw exitValue;
+ }
+
+ let lines = r.stdoutData.split(/\r?\n/);
+
+ for (var i = 0; i < lines.length; i++) {
+ let line = lines[i].split(/:/).map(unescape);
+ if (line.length <= 1) continue;
+
+ switch (line[0]) {
+ case "info":
+ if (line[1] !== "1") {
+ // protocol version not supported
+ throw {
+ result: 7,
+ errorDetails: EnigmailLocale.getString("keyserver.error.unsupported"),
+ pubKeys: []
+ };
+ }
+ break;
+ case "pub":
+ if (line.length >= 6) {
+ if (key) {
+ retObj.pubKeys.push(key);
+ key = null;
+ }
+ let dat = new Date(line[4] * 1000);
+ let month = String(dat.getMonth() + 101).substr(1);
+ let day = String(dat.getDate() + 100).substr(1);
+ key = {
+ keyId: line[1],
+ keyLen: line[3],
+ keyType: line[2],
+ created: dat.getFullYear() + "-" + month + "-" + day,
+ uid: [],
+ status: line[6]
+ };
+ }
+ break;
+ case "uid":
+ key.uid.push(EnigmailData.convertToUnicode(line[1].trim(), "utf-8"));
+ }
+ }
+
+ if (key) {
+ retObj.pubKeys.push(key);
+ }
+ }
+ catch (ex) {
+ retObj.result = ex.result;
+ retObj.errorDetails = ex.errorDetails;
+ throw retObj;
+ }
+
+ return retObj;
+ }
+};
+
+
+function getAccessType(keyserver) {
+ if (keyserver === null) {
+ keyserver = EnigmailKeyserverURIs.getDefaultKeyServer();
+ }
+
+
+ let srv = parseKeyserverUrl(keyserver);
+ switch (srv.protocol) {
+ case "keybase":
+ return accessKeyBase;
+ case "ldap":
+ case "ldaps":
+ return accessGnuPG;
+ case "vks":
+ return accessVksServer;
+ }
+
+ if (srv.host.search(/keys.openpgp.org$/i) >= 0) {
+ return accessVksServer;
+ }
+
+ if (EnigmailPrefs.getPref("useGpgKeysTool")) {
+ return accessGnuPG;
+ }
+
+ return accessHkpInternal;
+}
+
+
+/**
+ Object to handle VKS requests (for example keys.openpgp.org)
+ */
+const accessVksServer = {
+ /**
+ * Create the payload of VKS requests (currently upload only)
+ *
+ */
+ buildJsonPayload: function(actionFlag, searchTerms, locale) {
+ let payLoad = null,
+ keyData = "";
+
+ switch (actionFlag) {
+ case EnigmailConstants.UPLOAD_KEY:
+ keyData = EnigmailKeyRing.extractKey(false, searchTerms, null, {}, {});
+ if (keyData.length === 0) return null;
+
+ payLoad = JSON.stringify({
+ keytext: keyData
+ });
+ return payLoad;
+
+ case EnigmailConstants.GET_CONFIRMATION_LINK:
+ payLoad = JSON.stringify({
+ token: searchTerms.token,
+ addresses: searchTerms.addresses,
+ locale: [locale]
+ });
+ return payLoad;
+
+ case EnigmailConstants.DOWNLOAD_KEY:
+ case EnigmailConstants.SEARCH_KEY:
+ case EnigmailConstants.GET_SKS_CACERT:
+ return "";
+ }
+
+ // other actions are not yet implemented
+ return null;
+ },
+
+ /**
+ * return the URL and the HTTP access method for a given action
+ */
+ createRequestUrl: function(keyserver, actionFlag, searchTerm) {
+ let keySrv = parseKeyserverUrl(keyserver);
+ let contentType = "text/plain;charset=UTF-8";
+
+ let method = "GET";
+
+ let url = "https://" + keySrv.host + ":443";
+
+ if (actionFlag === EnigmailConstants.UPLOAD_KEY) {
+ url += "/vks/v1/upload";
+ method = "POST";
+ contentType = "application/json";
+ }
+ else if (actionFlag === EnigmailConstants.GET_CONFIRMATION_LINK) {
+ url += "/vks/v1/request-verify";
+ method = "POST";
+ contentType = "application/json";
+ }
+ else if (actionFlag === EnigmailConstants.DOWNLOAD_KEY || actionFlag === EnigmailConstants.SEARCH_KEY) {
+ if (searchTerm) {
+ let lookup = "/vks/";
+ if (searchTerm.indexOf("0x") === 0) {
+ searchTerm = searchTerm.substr(2);
+ if (searchTerm.length == 16 && searchTerm.search(/^[A-F0-9]+$/) === 0) {
+ lookup = "/vks/v1/by-keyid/" + searchTerm;
+ }
+ else if (searchTerm.length == 40 && searchTerm.search(/^[A-F0-9]+$/) === 0) {
+ lookup = "/vks/v1/by-fingerprint/" + searchTerm;
+ }
+ }
+ else {
+ try {
+ searchTerm = EnigmailFuncs.stripEmail(searchTerm);
+ }
+ catch (x) {}
+ lookup = "/vks/v1/by-email/" + searchTerm;
+ }
+ url += lookup;
+ }
+ }
+
+ return {
+ url: url,
+ host: keySrv.host,
+ method: method,
+ contentType: contentType
+ };
+ },
+
+ /**
+ * Upload, search or download keys from a keyserver
+ * @param actionFlag: Number - Keyserver Action Flags: from EnigmailConstants
+ * @param keyId: String - space-separated list of search terms or key IDs
+ * @param keyserver: String - keyserver URL (optionally incl. protocol)
+ * @param listener: optional Object implementing the KeySrvListener API (above)
+ *
+ * @return: Promise<Number (Status-ID)>
+ */
+ accessKeyServer: function(actionFlag, keyserver, keyId, listener) {
+ EnigmailLog.DEBUG(`keyserver.jsm: accessVksServer.accessKeyServer(${keyserver})\n`);
+ if (keyserver === null) {
+ keyserver = "keys.openpgp.org";
+ }
+
+ return new Promise((resolve, reject) => {
+ let xmlReq = null;
+ if (listener && typeof(listener) === "object") {
+ listener.onCancel = function() {
+ EnigmailLog.DEBUG(`keyserver.jsm: accessVksServer.accessKeyServer - onCancel() called\n`);
+ if (xmlReq) {
+ xmlReq.abort();
+ }
+ reject(createError(EnigmailConstants.KEYSERVER_ERR_ABORTED));
+ };
+ }
+ if (actionFlag === EnigmailConstants.REFRESH_KEY) {
+ // we don't (need to) distinguish between refresh and download for our internal protocol
+ actionFlag = EnigmailConstants.DOWNLOAD_KEY;
+ }
+
+ let uiLocale = EnigmailLocale.getUILocale();
+ let payLoad = this.buildJsonPayload(actionFlag, keyId, uiLocale);
+ if (payLoad === null) {
+ reject(createError(EnigmailConstants.KEYSERVER_ERR_UNKNOWN));
+ return;
+ }
+
+ let errorCode = 0;
+
+ xmlReq = new XMLHttpRequest();
+
+ xmlReq.onload = function _onLoad() {
+ EnigmailLog.DEBUG("keyserver.jsm: accessVksServer.onload(): status=" + xmlReq.status + "\n");
+ switch (actionFlag) {
+ case EnigmailConstants.UPLOAD_KEY:
+ case EnigmailConstants.GET_CONFIRMATION_LINK:
+
+ EnigmailLog.DEBUG("keyserver.jsm: accessVksServer.onload: " + xmlReq.responseText + "\n");
+ if (xmlReq.status >= 400) {
+ reject(createError(EnigmailConstants.KEYSERVER_ERR_SERVER_ERROR));
+ }
+ else {
+ resolve(xmlReq.responseText);
+ }
+ return;
+
+ case EnigmailConstants.SEARCH_KEY:
+ if (xmlReq.status === 404) {
+ // key not found
+ resolve("");
+ }
+ else if (xmlReq.status >= 400) {
+ reject(createError(EnigmailConstants.KEYSERVER_ERR_SERVER_ERROR));
+ }
+ else {
+ resolve(xmlReq.responseText);
+ }
+ return;
+
+ case EnigmailConstants.DOWNLOAD_KEY:
+ if (xmlReq.status >= 400 && xmlReq.status < 500) {
+ // key not found
+ resolve(1);
+ }
+ else if (xmlReq.status >= 500) {
+ EnigmailLog.DEBUG("keyserver.jsm: accessVksServer.onload: " + xmlReq.responseText + "\n");
+ reject(createError(EnigmailConstants.KEYSERVER_ERR_SERVER_ERROR));
+ }
+ else {
+ let errorMsgObj = {},
+ importedKeysObj = {};
+ let r = EnigmailKeyRing.importKey(null, false, xmlReq.responseText, "", errorMsgObj, importedKeysObj);
+ if (r === 0) {
+ resolve(importedKeysObj.value);
+ }
+ else {
+ reject(createError(EnigmailConstants.KEYSERVER_ERR_IMPORT_ERROR));
+ }
+ }
+ return;
+ }
+ resolve(-1);
+ };
+
+ xmlReq.onerror = function(e) {
+ EnigmailLog.DEBUG("keyserver.jsm: accessVksServer.accessKeyServer: onerror: " + e + "\n");
+ let err = EnigmailXhrUtils.createTCPErrorFromFailedXHR(e.target);
+ switch (err.type) {
+ case 'SecurityCertificate':
+ reject(createError(EnigmailConstants.KEYSERVER_ERR_CERTIFICATE_ERROR));
+ break;
+ case 'SecurityProtocol':
+ reject(createError(EnigmailConstants.KEYSERVER_ERR_SECURITY_ERROR));
+ break;
+ case 'Network':
+ reject(createError(EnigmailConstants.KEYSERVER_ERR_SERVER_UNAVAILABLE));
+ break;
+ }
+ reject(createError(EnigmailConstants.KEYSERVER_ERR_SERVER_UNAVAILABLE));
+ };
+
+ xmlReq.onloadend = function() {
+ EnigmailLog.DEBUG("keyserver.jsm: accessVksServer.accessKeyServer: loadEnd\n");
+ };
+
+ let {
+ url,
+ host,
+ method,
+ contentType
+ } = this.createRequestUrl(keyserver, actionFlag, keyId);
+
+ EnigmailLog.DEBUG(`keyserver.jsm: accessVksServer.accessKeyServer: requesting ${method} for ${url}\n`);
+ xmlReq.open(method, url);
+ xmlReq.setRequestHeader("Content-Type", contentType);
+ xmlReq.send(payLoad);
+ });
+ },
+
+ /**
+ * Download keys from a keyserver
+ * @param keyIDs: String - space-separated list of search terms or key IDs
+ * @param keyserver: String - keyserver URL (optionally incl. protocol)
+ * @param listener: optional Object implementing the KeySrvListener API (above)
+ *
+ * @return: Promise<...>
+ */
+ download: async function(keyIDs, keyserver, listener = null) {
+ EnigmailLog.DEBUG(`keyserver.jsm: accessVksServer.download(${keyIDs})\n`);
+ let keyIdArr = keyIDs.split(/ +/);
+ let retObj = {
+ result: 0,
+ errorDetails: "",
+ keyList: []
+ };
+
+ for (let i = 0; i < keyIdArr.length; i++) {
+ try {
+ let r = await this.accessKeyServer(EnigmailConstants.DOWNLOAD_KEY, keyserver, keyIdArr[i], listener);
+ if (Array.isArray(r)) {
+ retObj.keyList = retObj.keyList.concat(r);
+ }
+ }
+ catch (ex) {
+ retObj.result = ex.result;
+ retObj.errorDetails = ex.errorDetails;
+ throw retObj;
+ }
+
+ if (listener && "onProgress" in listener) {
+ listener.onProgress((i + 1) / keyIdArr.length * 100);
+ }
+ }
+
+ return retObj;
+ },
+
+ refresh: function(keyServer, listener = null) {
+ let keyList = EnigmailKeyRing.getAllKeys().keyList.map(keyObj => {
+ return "0x" + keyObj.fpr;
+ }).join(" ");
+
+ return this.download(keyList, keyServer, listener);
+ },
+
+ requestConfirmationLink: async function(keyserver, jsonFragment) {
+ EnigmailLog.DEBUG(`keyserver.jsm: accessVksServer.requestConfirmationLink()\n`);
+
+ let response = JSON.parse(jsonFragment);
+
+ let addr = [];
+
+ for (let email in response.status) {
+ if (response.status[email] !== "published") {
+ addr.push(email);
+ }
+ }
+
+ if (addr.length > 0) {
+ let r = await this.accessKeyServer(EnigmailConstants.GET_CONFIRMATION_LINK, keyserver, {
+ token: response.token,
+ addresses: addr
+ }, null);
+
+ if (typeof r === "string") {
+ return addr.length;
+ }
+ }
+
+ return 0;
+ },
+
+ /**
+ * Upload keys to a keyserver
+ * @param keyIDs: String - space-separated list of search terms or key IDs
+ * @param keyserver: String - keyserver URL (optionally incl. protocol)
+ * @param listener: optional Object implementing the KeySrvListener API (above)
+ *
+ * @return: Promise<...>
+ */
+ upload: async function(keyIDs, keyserver, listener = null) {
+ EnigmailLog.DEBUG(`keyserver.jsm: accessVksServer.upload(${keyIDs})\n`);
+ let keyIdArr = keyIDs.split(/ +/);
+ let retObj = {
+ result: 0,
+ errorDetails: "",
+ keyList: []
+ };
+
+ for (let i = 0; i < keyIdArr.length; i++) {
+ let keyObj = EnigmailKeyRing.getKeyById(keyIdArr[i]);
+
+ if (!keyObj.secretAvailable) {
+ // VKS keyservers only accept uploading own keys
+ retObj.result = 1;
+ retObj.errorDetails = "NO_SECRET_KEY_AVAILABLE";
+ throw retObj;
+ }
+
+ try {
+ let r = await this.accessKeyServer(EnigmailConstants.UPLOAD_KEY, keyserver, keyIdArr[i], listener);
+ if (typeof r === "string") {
+ retObj.keyList.push(keyIdArr[i]);
+ let req = await this.requestConfirmationLink(keyserver, r);
+
+ if (req >= 0) {
+ retObj.result = 0;
+ retObj.numEmails = req;
+ }
+ }
+ else {
+ retObj.result = r;
+ }
+ }
+ catch (ex) {
+ retObj.result = ex.result;
+ retObj.errorDetails = ex.errorDetails;
+ throw retObj;
+ }
+
+ if (listener && "onProgress" in listener) {
+ listener.onProgress((i + 1) / keyIdArr.length * 100);
+ }
+ }
+
+ return retObj;
+ },
+
+ /**
+ * Search for keys on a keyserver
+ * @param searchTerm: String - search term
+ * @param keyserver: String - keyserver URL (optionally incl. protocol)
+ * @param listener: optional Object implementing the KeySrvListener API (above)
+ *
+ * @return: Promise<Object>
+ * - result: Number
+ * - pubKeys: Array of Object:
+ * PubKeys: Object with:
+ * - keyId: String
+ * - keyLen: String
+ * - keyType: String
+ * - created: String (YYYY-MM-DD)
+ * - status: String: one of ''=valid, r=revoked, e=expired
+ * - uid: Array of Strings with UIDs
+ */
+ search: async function(searchTerm, keyserver, listener = null) {
+ EnigmailLog.DEBUG(`keyserver.jsm: accessVksServer.search(${searchTerm})\n`);
+ let retObj = {
+ result: 0,
+ errorDetails: "",
+ pubKeys: []
+ };
+ let key = null;
+
+ let searchArr = searchTerm.split(/ +/);
+
+ try {
+ for (let i in searchArr) {
+ let r = await this.accessKeyServer(EnigmailConstants.SEARCH_KEY, keyserver, searchArr[i], listener);
+
+ const cApi = EnigmailCryptoAPI();
+ let keyList = await cApi.getKeyListFromKeyBlock(r);
+
+ for (let k in keyList) {
+ key = {
+ keyId: keyList[k].fpr,
+ keyLen: "0",
+ keyType: "",
+ created: keyList[k].created,
+ uid: [keyList[k].name],
+ status: keyList[k].revoke ? "r" : ""
+ };
+
+ for (let uid of keyList[k].uids) {
+ key.uid.push(uid);
+ }
+
+ retObj.pubKeys.push(key);
+ }
+ }
+ }
+ catch (ex) {
+ retObj.result = ex.result;
+ retObj.errorDetails = ex.errorDetails;
+ throw retObj;
+ }
+
+ return retObj;
+ }
+};
+
+var EnigmailKeyServer = {
+ /**
+ * Download keys from a keyserver
+ * @param keyIDs: String - space-separated list of FPRs or key IDs
+ * @param keyserver: String - keyserver URL (optionally incl. protocol)
+ * @param listener: optional Object implementing the KeySrvListener API (above)
+ *
+ * @return: Promise<Object>
+ * Object: - result: Number - result Code (0 = OK),
+ * - keyList: Array of String - imported key FPR
+ */
+ download: function(keyIDs, keyserver = null, listener) {
+ let acc = getAccessType(keyserver);
+ return acc.download(keyIDs, keyserver, listener);
+ },
+
+ /**
+ * Upload keys to a keyserver
+ * @param keyIDs: String - space-separated list of key IDs or FPR
+ * @param keyserver: String - keyserver URL (optionally incl. protocol)
+ * @param listener: optional Object implementing the KeySrvListener API (above)
+ *
+ * @return: Promise<Object>
+ * Object: - result: Number - result Code (0 = OK),
+ * - keyList: Array of String - imported key FPR
+ */
+
+ upload: function(keyIDs, keyserver = null, listener) {
+ let acc = getAccessType(keyserver);
+ return acc.upload(keyIDs, keyserver, listener);
+ },
+
+ /**
+ * Search keys on a keyserver
+ * @param searchString: String - search term. Multiple email addresses can be search by spaces
+ * @param keyserver: String - keyserver URL (optionally incl. protocol)
+ * @param listener: optional Object implementing the KeySrvListener API (above)
+ *
+ * @return: Promise<Object>
+ * - result: Number
+ * - pubKeys: Array of Object:
+ * PubKeys: Object with:
+ * - keyId: String
+ * - keyLen: String
+ * - keyType: String
+ * - created: String (YYYY-MM-DD)
+ * - status: String: one of ''=valid, r=revoked, e=expired
+ * - uid: Array of Strings with UIDs
+ */
+ search: function(searchString, keyserver = null, listener) {
+ let acc = getAccessType(keyserver);
+ return acc.search(searchString, keyserver, listener);
+ },
+
+ /**
+ * Refresh all keys
+ *
+ * @param keyserver: String - keyserver URL (optionally incl. protocol)
+ * @param listener: optional Object implementing the KeySrvListener API (above)
+ *
+ * @return: Promise<resultStatus> (identical to download)
+ */
+ refresh: function(keyserver = null, listener) {
+ let acc = getAccessType(keyserver);
+ return acc.refresh(keyserver, listener);
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/keyserverUris.jsm
@@ -0,0 +1,121 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+const EXPORTED_SYMBOLS = ["EnigmailKeyserverURIs"];
+
+const EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs.jsm").EnigmailPrefs;
+const EnigmailOS = ChromeUtils.import("chrome://openpgp/content/modules/os.jsm").EnigmailOS;
+
+const KEYSERVER_PREF = "keyserver";
+const AUTO_KEYSERVER_SELECTION_PREF = "autoKeyServerSelection";
+
+const supportedProtocols = {
+ "hkps": "443",
+ "hkp": "11371",
+ "ldap": "389"
+};
+
+function buildUriFor(protocol, keyserver) {
+ return {
+ protocol: protocol,
+ domain: keyserver,
+ port: supportedProtocols[protocol]
+ };
+}
+
+function addUriOptionsForPoolKeyservers(keyserver, uris) {
+ if (keyserver === "hkps.pool.sks-keyservers.net") {
+ uris.push(buildUriFor("hkps", keyserver));
+ }
+ if (keyserver === "pool.sks-keyservers.net") {
+ uris.push(buildUriFor("hkps", "hkps.pool.sks-keyservers.net"));
+ uris.push(buildUriFor("hkp", keyserver));
+ }
+}
+
+function buildUriOptionsFor(keyserver) {
+ const uris = [];
+ const keyserverProtocolAndDomain = keyserver.split("://");
+ const protocolIncluded = keyserverProtocolAndDomain.length === 2;
+ const isPoolKeyserver = ["hkps.pool.sks-keyservers.net", "pool.sks-keyservers.net"].indexOf(keyserver) > -1;
+
+ if (isPoolKeyserver) {
+ addUriOptionsForPoolKeyservers(keyserver, uris);
+ }
+ else if (protocolIncluded) {
+ uris.push(buildUriFor(keyserverProtocolAndDomain[0].toLowerCase(), keyserverProtocolAndDomain[1]));
+ }
+ else {
+ uris.push(buildUriFor("hkps", keyserver));
+ uris.push(buildUriFor("hkp", keyserver));
+ }
+
+ return uris;
+}
+
+function getDefaultKeyServer() {
+ let keyservers = EnigmailPrefs.getPref(KEYSERVER_PREF).split(/\s*[,;]\s*/g);
+ return keyservers[0];
+}
+
+function getUserDefinedKeyserverURIs() {
+ const keyservers = EnigmailPrefs.getPref(KEYSERVER_PREF).split(/\s*[,;]\s*/g);
+ return EnigmailPrefs.getPref(AUTO_KEYSERVER_SELECTION_PREF) ? [keyservers[0]] : keyservers;
+}
+
+function combineIntoURI(protocol, domain, port) {
+ return protocol + "://" + domain + ":" + port;
+}
+
+function isValidProtocol(uri) {
+ return uri.match(/:\/\//) === null || /^(hkps|hkp|ldap):\/\//i.test(uri);
+}
+
+function validProtocolsExist() {
+ const validKeyserverUris = getUserDefinedKeyserverURIs().filter(isValidProtocol);
+ return validKeyserverUris.length > 0;
+}
+
+/**
+ * Construct the full URIs for making gpg requests.
+ * This takes the specified keyservers and adds the relevant protocol and port.
+ * When no specific protocol is defined by the user, 2 URIs will be built, for hkps and hkp.
+ *
+ * @return array of all URIs to try refreshing keys over
+ */
+function buildKeyserverUris() {
+ const uris = getUserDefinedKeyserverURIs().filter(isValidProtocol).map(function(keyserver) {
+ return buildUriOptionsFor(keyserver);
+ }).reduce(function(a, b) {
+ return a.concat(b);
+ });
+
+ return uris.map(function(uri) {
+ return combineIntoURI(uri.protocol, uri.domain, uri.port);
+ });
+}
+
+/**
+ * Checks if the keyservers specified are valid.
+ * Key refreshes will not be attempted without valid keyservers.
+ * A valid keyserver is one that is non-empty and consists of
+ * - the keyserverDomain
+ * - may include a protocol from hkps, hkp or ldap
+ * - may include the port
+ *
+ * @return true if keyservers exist and are valid, false otherwise.
+ */
+function validKeyserversExist() {
+ return EnigmailPrefs.getPref(KEYSERVER_PREF).trim() !== "" && validProtocolsExist();
+}
+
+var EnigmailKeyserverURIs = {
+ getDefaultKeyServer: getDefaultKeyServer,
+ buildKeyserverUris: buildKeyserverUris,
+ validKeyserversExist: validKeyserversExist
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/lazy.jsm
@@ -0,0 +1,24 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+const EXPORTED_SYMBOLS = ["EnigmailLazy"];
+
+
+var EnigmailLazy = {
+ loader: function(component, name) {
+ let holder = null;
+ return function() {
+ if (holder === null) {
+ component = component.replace(/^enigmail\//, "");
+ const into = ChromeUtils.import("chrome://openpgp/content/modules/" + component);
+ holder = into[name];
+ }
+ return holder;
+ };
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/locale.jsm
@@ -0,0 +1,109 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailLocale"];
+
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+
+
+var gEnigStringBundle = null;
+
+var EnigmailLocale = {
+ /**
+ * Get the application locale. Discrecommended - use getUILocale instead!
+ */
+ get: function() {
+ try {
+ return Cc["@mozilla.org/intl/nslocaleservice;1"].getService(Ci.nsILocaleService).getApplicationLocale();
+ } catch (ex) {
+ return {
+ getCategory: function(whatever) {
+ // always return the application locale
+ try {
+ // TB < 64
+ return Cc["@mozilla.org/intl/localeservice;1"].getService(Ci.mozILocaleService).getAppLocaleAsBCP47();
+ } catch (x) {
+ let a = Cc["@mozilla.org/intl/localeservice;1"].getService(Ci.mozILocaleService).appLocalesAsBCP47;
+ return (a.length > 0 ? a[0] : "");
+ }
+ }
+ };
+ }
+ },
+
+ /**
+ * Retrieve a localized string from the enigmail.properties stringbundle
+ *
+ * @param aStr: String - properties key
+ * @param subPhrases: String or Array of Strings - [Optional] additional input to be embedded
+ * in the resulting localized text
+ *
+ * @return String: the localized string
+ */
+ getString: function(aStr, subPhrases) {
+ if (!gEnigStringBundle) {
+ try {
+ /* HACK: The string bundle cache is cleared on addon shutdown, however it doesn't appear to do so reliably.
+ Errors can erratically happen on next load of the same file in certain instances. (at minimum, when strings are added/removed)
+ The apparently accepted solution to reliably load new versions is to always create bundles with a unique URL so as to bypass the cache.
+ This is accomplished by passing a random number in a parameter after a '?'. (this random ID is otherwise ignored)
+ The loaded string bundle is still cached on startup and should still be cleared out of the cache on addon shutdown.
+ This just bypasses the built-in cache for repeated loads of the same path so that a newly installed update loads cleanly. */
+ let bundlePath = "chrome://openpgp/locale/enigmail.properties?" + Math.random();
+ EnigmailLog.DEBUG("locale.jsm: loading stringBundle " + bundlePath + "\n");
+ let strBundleService = Cc["@mozilla.org/intl/stringbundle;1"].getService(Ci.nsIStringBundleService);
+ gEnigStringBundle = strBundleService.createBundle(bundlePath);
+ } catch (ex) {
+ EnigmailLog.ERROR("locale.jsm: Error in instantiating stringBundleService\n");
+ }
+ }
+
+ if (gEnigStringBundle) {
+ try {
+ if (subPhrases) {
+ if (typeof (subPhrases) == "string") {
+ return gEnigStringBundle.formatStringFromName(aStr, [subPhrases], 1);
+ }
+ else {
+ return gEnigStringBundle.formatStringFromName(aStr, subPhrases, subPhrases.length);
+ }
+ }
+ else {
+ return gEnigStringBundle.GetStringFromName(aStr);
+ }
+ } catch (ex) {
+ EnigmailLog.ERROR("locale.jsm: Error in querying stringBundleService for string '" + aStr + "'\n");
+ }
+ }
+ return aStr;
+ },
+
+ /**
+ * Get the locale for the User Interface
+ *
+ * @return String Locale (xx-YY)
+ */
+ getUILocale: function() {
+ let ps = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService);
+ let uaPref = ps.getBranch("general.useragent.");
+
+ try {
+ return uaPref.getComplexValue("locale", Ci.nsISupportsString).data;
+ } catch (e) {}
+ return this.get().getCategory("NSILOCALE_MESSAGES").substr(0, 5);
+ },
+
+ shutdown: function(reason) {
+ // flush string bundles on shutdown of the addon, such that it's no longer cached
+ try {
+ gEnigStringBundle = null;
+ let strBundleService = Cc["@mozilla.org/intl/stringbundle;1"].getService(Ci.nsIStringBundleService);
+ strBundleService.flushBundles();
+ } catch (e) {}
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/localizeHtml.jsm
@@ -0,0 +1,73 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailLocalizeHtml"];
+
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+const EnigmailBuildDate = ChromeUtils.import("chrome://openpgp/content/modules/buildDate.jsm").EnigmailBuildDate;
+const EnigmailApp = ChromeUtils.import("chrome://openpgp/content/modules/app.jsm").EnigmailApp;
+const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
+const EnigmailGpgAgent = ChromeUtils.import("chrome://openpgp/content/modules/gpgAgent.jsm").EnigmailGpgAgent;
+const Services = ChromeUtils.import("resource://gre/modules/Services.jsm").Services;
+
+function getEnigmailVersion() {
+ let versionStr = EnigmailApp.getVersion() + " (" + EnigmailBuildDate.built + ")";
+ return EnigmailLocale.getString("usingVersion", versionStr);
+}
+
+function getGpgWorking() {
+ var enigmailSvc = EnigmailCore.getService();
+
+ var agentStr;
+ if (enigmailSvc) {
+ agentStr = EnigmailLocale.getString("usingAgent", [EnigmailGpgAgent.agentType, EnigmailGpgAgent.agentPath.path]);
+ } else {
+ agentStr = EnigmailLocale.getString("agentError");
+
+ if (enigmailSvc && enigmailSvc.initializationError)
+ agentStr += "\n" + enigmailSvc.initializationError;
+ }
+
+ return agentStr;
+}
+
+var EnigmailLocalizeHtml = {
+ getAllElementsWithAttribute: function(doc, attribute) {
+ let matchingElements = [];
+ let allElements = doc.getElementsByTagName('*');
+ for (let i = 0, n = allElements.length; i < n; i++) {
+ if (allElements[i].getAttribute(attribute) !== null) {
+ matchingElements.push(allElements[i]);
+ }
+ }
+ return matchingElements;
+ },
+
+
+ onPageLoad: function(doc) {
+ let elem = this.getAllElementsWithAttribute(doc, "txtId");
+
+ for (let i = 0; i < elem.length; i++) {
+ let node = elem[i];
+ let txtId = node.getAttribute("txtId");
+ let param = node.getAttribute("txtParam");
+
+ switch (txtId) {
+ case "FNC_enigmailVersion":
+ node.innerHTML = getEnigmailVersion();
+ break;
+ case "FNC_isGpgWorking":
+ node.innerHTML = getGpgWorking();
+ break;
+ default:
+ node.innerHTML = EnigmailLocale.getString(txtId, param);
+ }
+
+ }
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/log.jsm
@@ -0,0 +1,169 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/* global dump: false */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailLog"];
+
+const EnigmailConsole = ChromeUtils.import("chrome://openpgp/content/modules/pipeConsole.jsm").EnigmailConsole;
+const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
+const EnigmailOS = ChromeUtils.import("chrome://openpgp/content/modules/os.jsm").EnigmailOS;
+
+const XPCOM_APPINFO = "@mozilla.org/xre/app-info;1";
+const NS_IOSERVICE_CONTRACTID = "@mozilla.org/network/io-service;1";
+
+const MAX_LOG_LEN = 2500;
+
+var EnigmailLog = {
+ level: 3,
+ data: null,
+ directory: null,
+ fileStream: null,
+
+ setLogLevel: function(newLogLevel) {
+ EnigmailLog.level = newLogLevel;
+ },
+
+ getLogLevel: function() {
+ return EnigmailLog.level;
+ },
+
+ setLogDirectory: function(newLogDirectory) {
+ EnigmailLog.directory = newLogDirectory + (EnigmailOS.isDosLike ? "\\" : "/");
+ EnigmailLog.createLogFiles();
+ },
+
+ createLogFiles: function() {
+ if (EnigmailLog.directory && (!EnigmailLog.fileStream) && EnigmailLog.level >= 5) {
+ EnigmailLog.fileStream = EnigmailFiles.createFileStream(EnigmailLog.directory + "enigdbug.txt");
+ }
+ },
+
+ onShutdown: function() {
+ if (EnigmailLog.fileStream) {
+ EnigmailLog.fileStream.close();
+ }
+ EnigmailLog.fileStream = null;
+ },
+
+ getLogData: function(version, prefs) {
+ let ioServ = Cc[NS_IOSERVICE_CONTRACTID].getService(Ci.nsIIOService);
+
+ let oscpu = "";
+ let platform = "";
+
+ try {
+ let httpHandler = ioServ.getProtocolHandler("http");
+ httpHandler = httpHandler.QueryInterface(Ci.nsIHttpProtocolHandler);
+ oscpu = httpHandler.oscpu;
+ platform = httpHandler.platform;
+ }
+ catch (ex) {}
+
+ let data = "Enigmail version " + version + "\n" +
+ "OS/CPU=" + oscpu + "\n" +
+ "Platform=" + platform + "\n" +
+ "Non-default preference values:\n";
+
+ let p = prefs.getPrefBranch().getChildList("");
+
+ for (let i in p) {
+ if (prefs.getPrefBranch().prefHasUserValue(p[i])) {
+ data += p[i] + ": " + prefs.getPref(p[i]) + "\n";
+ }
+ }
+
+ let otherPref = ["dom.workers.maxPerDomain"];
+ let root = prefs.getPrefRoot();
+ for (let op of otherPref) {
+ try {
+ data += op + ": " + root.getIntPref(op) + "\n";
+ }
+ catch (ex) {
+ data += ex.toString() + "\n";
+ }
+ }
+ return data + "\n" + EnigmailLog.data.join("");
+ },
+
+ WRITE: function(str) {
+ function withZeroes(val, digits) {
+ return ("0000" + val.toString()).substr(-digits);
+ }
+
+ var d = new Date();
+ var datStr = d.getFullYear() + "-" + withZeroes(d.getMonth() + 1, 2) + "-" + withZeroes(d.getDate(), 2) + " " + withZeroes(d.getHours(), 2) + ":" + withZeroes(d.getMinutes(), 2) + ":" +
+ withZeroes(d.getSeconds(), 2) + "." + withZeroes(d.getMilliseconds(), 3) + " ";
+ if (EnigmailLog.level >= 4)
+ dump(datStr + str);
+
+ if (EnigmailLog.data === null) {
+ EnigmailLog.data = [];
+ let appInfo = Cc[XPCOM_APPINFO].getService(Ci.nsIXULAppInfo);
+ EnigmailLog.WRITE("Mozilla Platform: " + appInfo.name + " " + appInfo.version + "\n");
+ }
+ // truncate first part of log data if it grow too much
+ if (EnigmailLog.data.length > MAX_LOG_LEN) {
+ EnigmailLog.data.splice(0, 200);
+ }
+
+ EnigmailLog.data.push(datStr + str);
+
+ if (EnigmailLog.fileStream) {
+ EnigmailLog.fileStream.write(datStr, datStr.length);
+ EnigmailLog.fileStream.write(str, str.length);
+ }
+ },
+
+ DEBUG: function(str) {
+ try {
+ EnigmailLog.WRITE("[DEBUG] " + str);
+ }
+ catch (ex) {}
+ },
+
+ WARNING: function(str) {
+ EnigmailLog.WRITE("[WARN] " + str);
+ EnigmailConsole.write(str);
+ },
+
+ ERROR: function(str) {
+ try {
+ var consoleSvc = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService);
+ var scriptError = Cc["@mozilla.org/scripterror;1"].createInstance(Ci.nsIScriptError);
+ scriptError.init(str, null, null, 0, 0, scriptError.errorFlag, "Enigmail");
+ consoleSvc.logMessage(scriptError);
+ }
+ catch (ex) {}
+
+ EnigmailLog.WRITE("[ERROR] " + str);
+ },
+
+ CONSOLE: function(str) {
+ if (EnigmailLog.level >= 3) {
+ EnigmailLog.WRITE("[CONSOLE] " + str);
+ }
+
+ EnigmailConsole.write(str);
+ },
+
+ /**
+ * Log an exception including the stack trace
+ *
+ * referenceInfo: String - arbitraty text to write before the exception is logged
+ * ex: exception object
+ */
+ writeException: function(referenceInfo, ex) {
+ EnigmailLog.ERROR(referenceInfo + ": caught exception: " +
+ ex.name + "\n" +
+ "Message: '" + ex.message + "'\n" +
+ "File: " + ex.fileName + "\n" +
+ "Line: " + ex.lineNumber + "\n" +
+ "Stack: " + ex.stack + "\n");
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/mime.jsm
@@ -0,0 +1,525 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailMime"];
+
+const jsmime = ChromeUtils.import("resource:///modules/jsmime.jsm").jsmime;
+const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
+const EnigmailRNG = ChromeUtils.import("chrome://openpgp/content/modules/rng.jsm").EnigmailRNG;
+const EnigmailStreams = ChromeUtils.import("chrome://openpgp/content/modules/streams.jsm").EnigmailStreams;
+
+var EnigmailMime = {
+ /***
+ * create a string of random characters suitable to use for a boundary in a
+ * MIME message following RFC 2045
+ *
+ * @return: string of 33 random characters and digits
+ */
+ createBoundary: function() {
+ return EnigmailRNG.generateRandomString(33);
+ },
+
+ /***
+ * determine the "boundary" part of a mail content type.
+ *
+ * @contentTypeStr: the string containing all parts of a content-type.
+ * (e.g. multipart/mixed; boundary="xyz") --> returns "xyz"
+ *
+ * @return: String containing the boundary parameter; or ""
+ */
+
+ getBoundary: function(contentTypeStr) {
+ return EnigmailMime.getParameter(contentTypeStr, "boundary");
+ },
+
+ /***
+ * determine the "protocol" part of a mail content type.
+ *
+ * @contentTypeStr: the string containing all parts of a content-type.
+ * (e.g. multipart/signed; protocol="xyz") --> returns "xyz"
+ *
+ * @return: String containing the protocol parameter; or ""
+ */
+
+ getProtocol: function(contentTypeStr) {
+ return EnigmailMime.getParameter(contentTypeStr, "protocol");
+ },
+
+ /***
+ * determine an arbitrary "parameter" part of a mail header.
+ *
+ * @param headerStr: the string containing all parts of the header.
+ * @param parameter: the parameter we are looking for
+ *
+ *
+ * 'multipart/signed; protocol="xyz"', 'protocol' --> returns "xyz"
+ *
+ * @return: String containing the parameter; or ""
+ */
+
+ getParameter: function(headerStr, parameter) {
+ let paramsArr = EnigmailMime.getAllParameters(headerStr);
+ parameter = parameter.toLowerCase();
+ if (parameter in paramsArr) {
+ return paramsArr[parameter];
+ }
+ else
+ return "";
+ },
+
+ /***
+ * get all parameter attributes of a mail header.
+ *
+ * @param headerStr: the string containing all parts of the header.
+ *
+ * @return: Array of Object containing the key value pairs
+ *
+ * 'multipart/signed; protocol="xyz"'; boundary="xxx"
+ * --> returns [ ["protocol": "xyz"], ["boundary": "xxx"] ]
+ */
+
+ getAllParameters: function(headerStr) {
+
+ headerStr = headerStr.replace(/[\r\n]+[ \t]+/g, "");
+ let hdrMap = jsmime.headerparser.parseParameterHeader(";" + headerStr, true, true);
+
+ let paramArr = [];
+ let i = hdrMap.entries();
+ let p = i.next();
+ while (p.value) {
+ paramArr[p.value[0].toLowerCase()] = p.value[1];
+ p = i.next();
+ }
+
+ return paramArr;
+ },
+
+ /***
+ * determine the "charset" part of a mail content type.
+ *
+ * @contentTypeStr: the string containing all parts of a content-type.
+ * (e.g. multipart/mixed; charset="utf-8") --> returns "utf-8"
+ *
+ * @return: String containing the charset parameter; or null
+ */
+
+ getCharset: function(contentTypeStr) {
+ return EnigmailMime.getParameter(contentTypeStr, "charset");
+ },
+
+ /**
+ * Convert a MIME header value into a UTF-8 encoded representation following RFC 2047
+ */
+ encodeHeaderValue: function(aStr) {
+ let ret = "";
+
+ if (aStr.search(/[^\x01-\x7F]/) >= 0) { // eslint-disable-line no-control-regex
+ let s = EnigmailData.convertFromUnicode(aStr, "utf-8");
+ ret = "=?UTF-8?B?" + btoa(s) + "?=";
+ }
+ else {
+ ret = aStr;
+ }
+
+ return ret;
+ },
+
+ /**
+ * format MIME header with maximum length of 72 characters.
+ */
+ formatHeaderData: function(hdrValue) {
+ let header;
+ if (Array.isArray(hdrValue)) {
+ header = hdrValue.join("").split(" ");
+ }
+ else {
+ header = hdrValue.split(/ +/);
+ }
+
+ let line = "";
+ let lines = [];
+
+ for (let i = 0; i < header.length; i++) {
+ if (line.length + header[i].length >= 72) {
+ lines.push(line + "\r\n");
+ line = " " + header[i];
+ }
+ else {
+ line += " " + header[i];
+ }
+ }
+
+ lines.push(line);
+
+ return lines.join("").trim();
+ },
+
+ /**
+ * Correctly encode and format a set of email addresses for RFC 2047
+ */
+ formatEmailAddress: function(addressData) {
+ const adrArr = addressData.split(/, */);
+
+ for (let i in adrArr) {
+ try {
+ const m = adrArr[i].match(/(.*[\w\s]+?)<([\w-][\w.-]+@[\w-][\w.-]+[a-zA-Z]{1,4})>/);
+ if (m && m.length == 3) {
+ adrArr[i] = this.encodeHeaderValue(m[1]) + " <" + m[2] + ">";
+ }
+ }
+ catch (ex) {}
+ }
+
+ return adrArr.join(", ");
+ },
+
+ /**
+ * Extract the subject from the 1st line of the message body, if the message body starts
+ * with: "Subject: ...\r?\n\r?\n".
+ *
+ * @param msgBody - String: message body
+ *
+ * @return
+ * if subject is found:
+ * Object:
+ * - messageBody - String: message body without subject
+ * - subject - String: extracted subject
+ *
+ * if subject not found: null
+ */
+ extractSubjectFromBody: function(msgBody) {
+ let m = msgBody.match(/^(\r?\n?Subject: [^\r\n]+\r?\n\r?\n)/i);
+ if (m && m.length > 0) {
+ let subject = m[0].replace(/[\r\n]/g, "");
+ subject = subject.substr(9);
+ msgBody = msgBody.substr(m[0].length);
+
+ return {
+ messageBody: msgBody,
+ subject: subject
+ };
+ }
+
+ return null;
+ },
+
+ /***
+ * determine if the message data contains a first mime part with content-type = "text/rfc822-headers"
+ * if so, extract the corresponding field(s)
+ */
+
+ extractProtectedHeaders: function(contentData) {
+ // find first MIME delimiter. Anything before that delimiter is the top MIME structure
+ let m = contentData.search(/^--/m);
+
+ let protectedHdr = ["subject", "date", "from",
+ "to", "cc", "reply-to", "references",
+ "newsgroups", "followup-to", "message-id"
+ ];
+ let newHeaders = {};
+
+ // read headers of first MIME part and extract the boundary parameter
+ let outerHdr = Cc["@mozilla.org/messenger/mimeheaders;1"].createInstance(Ci.nsIMimeHeaders);
+ outerHdr.initialize(contentData.substr(0, m));
+
+ let ct = outerHdr.extractHeader("content-type", false) || "";
+ if (ct === "") return null;
+
+ let startPos = -1,
+ endPos = -1,
+ bound = "";
+
+ if (ct.search(/^multipart\//i) === 0) {
+ // multipart/xyz message type
+ if (m < 5) {
+ return null;
+ }
+
+
+ bound = EnigmailMime.getBoundary(ct);
+ if (bound === "") return null;
+
+ // search for "outer" MIME delimiter(s)
+ let r = new RegExp("^--" + bound, "mg");
+
+ startPos = -1;
+ endPos = -1;
+
+ // 1st match: start of 1st MIME-subpart
+ let match = r.exec(contentData);
+ if (match && match.index) {
+ startPos = match.index;
+ }
+
+ // 2nd match: end of 1st MIME-subpart
+ match = r.exec(contentData);
+ if (match && match.index) {
+ endPos = match.index;
+ }
+
+ if (startPos < 0 || endPos < 0) return null;
+ }
+ else {
+ startPos = contentData.length;
+ endPos = 0;
+ }
+
+ let headers = Cc["@mozilla.org/messenger/mimeheaders;1"].createInstance(Ci.nsIMimeHeaders);
+ headers.initialize(contentData.substring(0, startPos));
+
+ // we got a potentially protected header. Let's check ...
+ ct = headers.extractHeader("content-type", false) || "";
+ if (this.getParameter(ct, "protected-headers").search(/^v1$/i) !== 0) return null;
+
+ for (let i in protectedHdr) {
+ if (headers.hasHeader(protectedHdr[i])) {
+ newHeaders[protectedHdr[i]] = jsmime.headerparser.decodeRFC2047Words(headers.extractHeader(protectedHdr[i], true)) || undefined;
+ }
+ }
+
+ // contentBody holds the complete 1st MIME part
+ let contentBody = contentData.substring(startPos + bound.length + 3, endPos);
+ let i = contentBody.search(/^[A-Za-z]/m); // skip empty lines
+ if (i > 0) {
+ contentBody = contentBody.substr(i);
+ }
+
+ headers.initialize(contentBody);
+
+ let innerCt = headers.extractHeader("content-type", false) || "";
+
+ if (innerCt.search(/^text\/rfc822-headers/i) === 0) {
+
+ let charset = EnigmailMime.getCharset(innerCt);
+ let ctt = headers.extractHeader("content-transfer-encoding", false) || "";
+
+ // determine where the headers end and the MIME-subpart body starts
+ let bodyStartPos = contentBody.search(/\r?\n\s*\r?\n/) + 1;
+
+ if (bodyStartPos < 10) return null;
+
+ bodyStartPos += contentBody.substr(bodyStartPos).search(/^[A-Za-z]/m);
+
+ let ctBodyData = contentBody.substr(bodyStartPos);
+
+ if (ctt.search(/^base64/i) === 0) {
+ ctBodyData = EnigmailData.decodeBase64(ctBodyData) + "\n";
+ }
+ else if (ctt.search(/^quoted-printable/i) === 0) {
+ ctBodyData = EnigmailData.decodeQuotedPrintable(ctBodyData) + "\n";
+ }
+
+ if (charset) {
+ ctBodyData = EnigmailData.convertToUnicode(ctBodyData, charset);
+ }
+
+ // get the headers of the MIME-subpart body --> that's the ones we need
+ let bodyHdr = Cc["@mozilla.org/messenger/mimeheaders;1"].createInstance(Ci.nsIMimeHeaders);
+ bodyHdr.initialize(ctBodyData);
+
+ for (let i in protectedHdr) {
+ if (bodyHdr.hasHeader(protectedHdr[i])) {
+ newHeaders[protectedHdr[i]] = jsmime.headerparser.decodeRFC2047Words(bodyHdr.extractHeader(protectedHdr[i], true)) || undefined;
+ }
+ }
+ }
+ else {
+ startPos = -1;
+ endPos = -1;
+ }
+
+ return {
+ newHeaders: newHeaders,
+ startPos: startPos,
+ endPos: endPos,
+ securityLevel: 0
+ };
+ },
+
+ /**
+ * Get the part number from a URI spec (e.g. mailbox:///folder/xyz?part=1.2.3.5)
+ *
+ * @param spec: String - the URI spec to inspect
+ *
+ * @return String: the mime part number (or "" if none found)
+ */
+ getMimePartNumber: function(spec) {
+ let m = spec.match(/([\?&]part=)(\d+(\.\d+)*)/);
+
+ if (m && m.length >= 3) {
+ return m[2];
+ }
+
+ return "";
+ },
+
+ /**
+ * Try to determine if the message structure is a known MIME structure,
+ * based on the MIME part number and the uriSpec.
+ *
+ * @param mimePartNumber: String - the MIME part we are requested to decrypt
+ * @param uriSpec: String - the URI spec of the message (or msg part) loaded by TB
+ *
+ * @return Boolean: true: regular message structure, MIME part is safe to be decrypted
+ * false: otherwise
+ */
+ isRegularMimeStructure: function(mimePartNumber, uriSpec, acceptSubParts = false) {
+ if (mimePartNumber.length === 0) return true;
+
+ if (acceptSubParts && (mimePartNumber.search(/^1(\.1)*$/) === 0)) return true;
+ if (mimePartNumber === "1") return true;
+
+ if (!uriSpec) return true;
+
+ // is the message a subpart of a complete attachment?
+ let msgPart = this.getMimePartNumber(uriSpec);
+ if (msgPart.length > 0) {
+ // load attached messages
+ if (mimePartNumber.indexOf(msgPart) === 0 &&
+ mimePartNumber.substr(msgPart.length).search(/^(\.1)+$/) === 0) return true;
+
+ // load attachments of attached messages
+ if (msgPart.indexOf(mimePartNumber) === 0 &&
+ uriSpec.search(/[\?&]filename=/) > 0) return true;
+ }
+
+ return false;
+ },
+
+
+ /**
+ * Parse a MIME message and return a tree structur of TreeObject
+ *
+ * @param url: String - the URL to load and parse
+ * @param getBody: Boolean - if true, delivers the body text of each MIME part
+ * @param callbackFunc Function - the callback function that is called asynchronously
+ * when parsing is complete.
+ * Function signature: callBackFunc(TreeObject)
+ *
+ * @return undefined
+ */
+ getMimeTreeFromUrl: function(url, getBody = false, callbackFunc) {
+ function onData(data) {
+ let tree = getMimeTree(data, getBody);
+ callbackFunc(tree);
+ }
+
+ let chan = EnigmailStreams.createChannel(url);
+ let bufferListener = EnigmailStreams.newStringStreamListener(onData);
+ chan.asyncOpen(bufferListener, null);
+ },
+
+ getMimeTree: getMimeTree
+
+};
+
+/**
+ * Parse a MIME message and return a tree structure of TreeObject.
+ *
+ * TreeObject contains the following main parts:
+ * - partNum: String
+ * - headers: Map, containing all headers.
+ * Special headers for contentType and charset
+ * - body: String, if getBody == true
+ * - subParts: Array of TreeObject
+ *
+ * @param mimeStr: String - a MIME structure to parse
+ * @param getBody: Boolean - if true, delivers the body text of each MIME part
+ *
+ * @return TreeObject, or NULL in case of failure
+ */
+function getMimeTree(mimeStr, getBody = false) {
+
+ let mimeTree = {
+ partNum: "",
+ headers: null,
+ body: "",
+ parent: null,
+ subParts: []
+ },
+ stack = [],
+ currentPart = "",
+ currPartNum = "";
+
+ const jsmimeEmitter = {
+
+ createPartObj: function(partNum, headers, parent) {
+ let ct;
+
+ if (headers.has("content-type")) {
+ ct = headers.contentType.type;
+ let it = headers.get("content-type").entries();
+ for (let i of it) {
+ ct += '; ' + i[0] + '="' + i[1] + '"';
+ }
+ }
+
+ return {
+ partNum: partNum,
+ headers: headers,
+ fullContentType: ct,
+ body: "",
+ parent: parent,
+ subParts: []
+ };
+ },
+
+ /** JSMime API **/
+ startMessage: function() {
+ currentPart = mimeTree;
+ },
+
+ endMessage: function() {},
+
+ startPart: function(partNum, headers) {
+ //dump("mime.jsm: jsmimeEmitter.startPart: partNum=" + partNum + "\n");
+ partNum = "1" + (partNum !== "" ? "." : "") + partNum;
+ let newPart = this.createPartObj(partNum, headers, currentPart);
+
+ if (partNum.indexOf(currPartNum) === 0) {
+ // found sub-part
+ currentPart.subParts.push(newPart);
+ }
+ else {
+ // found same or higher level
+ currentPart.subParts.push(newPart);
+ }
+ currPartNum = partNum;
+ currentPart = newPart;
+ },
+ endPart: function(partNum) {
+ //dump("mime.jsm: jsmimeEmitter.startPart: partNum=" + partNum + "\n");
+ currentPart = currentPart.parent;
+ },
+
+ deliverPartData: function(partNum, data) {
+ //dump("mime.jsm: jsmimeEmitter.deliverPartData: partNum=" + partNum + " / " + typeof data + "\n");
+ if (typeof(data) === "string") {
+ currentPart.body += data;
+ }
+ else {
+ currentPart.body += EnigmailData.arrayBufferToString(data);
+ }
+ }
+ };
+
+ let opt = {
+ strformat: "unicode",
+ bodyformat: getBody ? "decode" : "none"
+ };
+
+
+ try {
+ let p = new jsmime.MimeParser(jsmimeEmitter, opt);
+ p.deliverData(mimeStr);
+ return mimeTree.subParts[0];
+ }
+ catch (ex) {
+ return null;
+ }
+}
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/mimeDecrypt.jsm
@@ -0,0 +1,800 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailMimeDecrypt"];
+
+/**
+ * Module for handling PGP/MIME encrypted messages
+ * implemented as an XPCOM object
+ */
+
+const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
+const EnigmailVerify = ChromeUtils.import("chrome://openpgp/content/modules/mimeVerify.jsm").EnigmailVerify;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
+const EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs.jsm").EnigmailPrefs;
+const EnigmailDecryption = ChromeUtils.import("chrome://openpgp/content/modules/decryption.jsm").EnigmailDecryption;
+var EnigmailMime = ChromeUtils.import("chrome://openpgp/content/modules/mime.jsm").EnigmailMime;
+const EnigmailURIs = ChromeUtils.import("chrome://openpgp/content/modules/uris.jsm").EnigmailURIs;
+const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
+const EnigmailSingletons = ChromeUtils.import("chrome://openpgp/content/modules/singletons.jsm").EnigmailSingletons;
+const EnigmailHttpProxy = ChromeUtils.import("chrome://openpgp/content/modules/httpProxy.jsm").EnigmailHttpProxy;
+const EnigmailCryptoAPI = ChromeUtils.import("chrome://openpgp/content/modules/cryptoAPI.jsm").EnigmailCryptoAPI;
+const EnigmailAutocrypt = ChromeUtils.import("chrome://openpgp/content/modules/autocrypt.jsm").EnigmailAutocrypt;
+const EnigmailCompat = ChromeUtils.import("chrome://openpgp/content/modules/compat.jsm").EnigmailCompat;
+
+const APPSHELL_MEDIATOR_CONTRACTID = "@mozilla.org/appshell/window-mediator;1";
+const PGPMIME_JS_DECRYPTOR_CONTRACTID = "@mozilla.org/mime/pgp-mime-js-decrypt;1";
+const PGPMIME_JS_DECRYPTOR_CID = Components.ID("{7514cbeb-2bfd-4b2c-829b-1a4691fa0ac8}");
+
+const ENCODING_DEFAULT = 0;
+const ENCODING_BASE64 = 1;
+const ENCODING_QP = 2;
+
+const LAST_MSG = EnigmailSingletons.lastDecryptedMessage;
+
+var gDebugLogLevel = 0;
+
+var gNumProc = 0;
+
+var EnigmailMimeDecrypt = {
+ /**
+ * create a new instance of a PGP/MIME decryption handler
+ */
+ newPgpMimeHandler: function() {
+ return new MimeDecryptHandler();
+ },
+
+ /**
+ * Return a fake empty attachment with information that the message
+ * was not decrypted
+ *
+ * @return {String}: MIME string (HTML text)
+ */
+ emptyAttachment: function() {
+ EnigmailLog.DEBUG("mimeDecrypt.jsm: emptyAttachment()\n");
+
+ let encPart = EnigmailLocale.getString("mimeDecrypt.encryptedPart.attachmentLabel");
+ let concealed = EnigmailLocale.getString("mimeDecrypt.encryptedPart.concealedData");
+ let retData =
+ `Content-Type: message/rfc822; name="${encPart}.eml"
+Content-Transfer-Encoding: 7bit
+Content-Disposition: attachment; filename="${encPart}.eml"
+
+Content-Type: text/html
+
+<p><i>${concealed}</i></p>
+`;
+ return retData;
+ },
+
+ /**
+ * Wrap the decrypted output into a message/rfc822 attachment
+ *
+ * @param {String} decryptingMimePartNum: requested MIME part number
+ * @param {Object} uri: nsIURI object of the decrypted message
+ *
+ * @return {String}: prefix for message data
+ */
+ pretendAttachment: function(decryptingMimePartNum, uri) {
+ if (decryptingMimePartNum === "1" || !uri) return "";
+
+ let msg = "";
+ let mimePartNumber = EnigmailMime.getMimePartNumber(uri.spec);
+
+ if (mimePartNumber === decryptingMimePartNum + ".1") {
+ msg = 'Content-Type: message/rfc822; name="attachment.eml"\r\n' +
+ 'Content-Transfer-Encoding: 7bit\r\n' +
+ 'Content-Disposition: attachment; filename="attachment.eml"\r\n\r\n';
+
+ try {
+ let dbHdr = uri.QueryInterface(Ci.nsIMsgMessageUrl).messageHeader;
+ if (dbHdr.subject) msg += `Subject: ${dbHdr.subject}\r\n`;
+ if (dbHdr.author) msg += `From: ${dbHdr.author}\r\n`;
+ if (dbHdr.recipients) msg += `To: ${dbHdr.recipients}\r\n`;
+ if (dbHdr.ccList) msg += `Cc: ${dbHdr.ccList}\r\n`;
+ } catch (x) {}
+ }
+
+ return msg;
+ }
+};
+
+////////////////////////////////////////////////////////////////////
+// handler for PGP/MIME encrypted messages
+// data is processed from libmime -> nsPgpMimeProxy
+
+function MimeDecryptHandler() {
+
+ EnigmailLog.DEBUG("mimeDecrypt.jsm: MimeDecryptHandler()\n"); // always log this one
+ this.mimeSvc = null;
+ this.initOk = false;
+ this.boundary = "";
+ this.pipe = null;
+ this.closePipe = false;
+ this.statusStr = "";
+ this.outQueue = "";
+ this.dataLength = 0;
+ this.bytesWritten = 0;
+ this.mimePartCount = 0;
+ this.headerMode = 0;
+ this.xferEncoding = ENCODING_DEFAULT;
+ this.matchedPgpDelimiter = 0;
+ this.exitCode = null;
+ this.msgWindow = null;
+ this.msgUriSpec = null;
+ this.returnStatus = null;
+ this.proc = null;
+ this.statusDisplayed = false;
+ this.uri = null;
+ this.backgroundJob = false;
+ this.decryptedHeaders = {};
+ this.mimePartNumber = "";
+ this.dataIsBase64 = null;
+ this.base64Cache = "";
+
+ if (EnigmailCompat.isMessageUriInPgpMime()) {
+ this.onDataAvailable = this.onDataAvailable68;
+ } else {
+ this.onDataAvailable = this.onDataAvailable60;
+ }
+}
+
+MimeDecryptHandler.prototype = {
+ inStream: Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream),
+
+ onStartRequest: function(request, uri) {
+ if (!EnigmailCore.getService()) // Ensure Enigmail is initialized
+ return;
+ EnigmailLog.DEBUG("mimeDecrypt.jsm: onStartRequest\n"); // always log this one
+
+ ++gNumProc;
+ if (gNumProc > EnigmailPrefs.getPref("maxNumProcesses")) {
+ EnigmailLog.DEBUG("mimeDecrypt.jsm: number of parallel requests above threshold - ignoring requst\n");
+ return;
+ }
+
+ this.initOk = true;
+ this.mimeSvc = request.QueryInterface(Ci.nsIPgpMimeProxy);
+ if ("mimePart" in this.mimeSvc) {
+ this.mimePartNumber = this.mimeSvc.mimePart;
+ } else {
+ this.mimePartNumber = "";
+ }
+
+ if ("messageURI" in this.mimeSvc) {
+ this.uri = this.mimeSvc.messageURI;
+ if (this.uri) {
+ EnigmailLog.DEBUG("mimeDecrypt.jsm: onStartRequest: uri='" + this.uri.spec + "'\n");
+ }
+ else {
+ EnigmailLog.DEBUG("mimeDecrypt.jsm: onStartRequest: uri=null\n");
+ }
+ } else {
+ if (uri) {
+ this.uri = uri.QueryInterface(Ci.nsIURI);
+ EnigmailLog.DEBUG("mimeDecrypt.jsm: onStartRequest: uri='" + this.uri.spec + "'\n");
+ }
+ }
+ this.pipe = null;
+ this.closePipe = false;
+ this.exitCode = null;
+ this.msgWindow = EnigmailVerify.lastMsgWindow;
+ this.msgUriSpec = EnigmailVerify.lastMsgUri;
+
+ this.statusDisplayed = false;
+ this.returnStatus = null;
+ this.dataLength = 0;
+ this.decryptedData = "";
+ this.mimePartCount = 0;
+ this.bytesWritten = 0;
+ this.matchedPgpDelimiter = 0;
+ this.dataIsBase64 = null;
+ this.base64Cache = "";
+ this.outQueue = "";
+ this.statusStr = "";
+ this.headerMode = 0;
+ this.decryptedHeaders = {};
+ this.xferEncoding = ENCODING_DEFAULT;
+ this.boundary = EnigmailMime.getBoundary(this.mimeSvc.contentType);
+
+ if (!this.isReloadingLastMessage()) {
+ EnigmailSingletons.clearLastDecryptedMessage();
+ }
+ },
+
+ processData: function(data) {
+ // detect MIME part boundary
+ if (data.indexOf(this.boundary) >= 0) {
+ LOCAL_DEBUG("mimeDecrypt.jsm: processData: found boundary\n");
+ ++this.mimePartCount;
+ this.headerMode = 1;
+ return;
+ }
+
+ // found PGP/MIME "body"
+ if (this.mimePartCount == 2) {
+
+ if (this.headerMode == 1) {
+ // we are in PGP/MIME main part headers
+ if (data.search(/\r|\n/) === 0) {
+ // end of Mime-part headers reached
+ this.headerMode = 2;
+ return;
+ } else {
+ if (data.search(/^content-transfer-encoding:\s*/i) >= 0) {
+ // extract content-transfer-encoding
+ data = data.replace(/^content-transfer-encoding:\s*/i, "");
+ data = data.replace(/;.*/, "").toLowerCase().trim();
+ if (data.search(/base64/i) >= 0) {
+ this.xferEncoding = ENCODING_BASE64;
+ } else if (data.search(/quoted-printable/i) >= 0) {
+ this.xferEncoding = ENCODING_QP;
+ }
+
+ }
+ }
+ } else {
+ // PGP/MIME main part body
+ if (this.xferEncoding == ENCODING_QP) {
+ this.cacheData(EnigmailData.decodeQuotedPrintable(data));
+ } else {
+ this.cacheData(data);
+ }
+ }
+ }
+ },
+
+ /**
+ * onDataAvailable for TB <= 66
+ */
+ onDataAvailable60: function(req, dummy, stream, offset, count) {
+
+ // get data from libmime
+ if (!this.initOk) return;
+ this.inStream.init(stream);
+
+ if (count > 0) {
+ var data = this.inStream.read(count);
+
+ if (this.mimePartCount == 0 && this.dataIsBase64 === null) {
+ // try to determine if this could be a base64 encoded message part
+ this.dataIsBase64 = this.isBase64Encoding(data);
+ }
+
+ if (!this.dataIsBase64) {
+ if (data.search(/[\r\n][^\r\n]+[\r\n]/) >= 0) {
+ // process multi-line data line by line
+ let lines = data.replace(/\r\n/g, "\n").split(/\n/);
+
+ for (let i = 0; i < lines.length; i++) {
+ this.processData(lines[i] + "\r\n");
+ }
+ } else
+ this.processData(data);
+ } else {
+ this.base64Cache += data;
+ }
+ }
+ },
+
+ /**
+ * onDataAvailable for TB >= 68
+ */
+ onDataAvailable68: function(req, stream, offset, count) {
+
+ // get data from libmime
+ if (!this.initOk) return;
+ this.inStream.init(stream);
+
+ if (count > 0) {
+ var data = this.inStream.read(count);
+
+ if (this.mimePartCount == 0 && this.dataIsBase64 === null) {
+ // try to determine if this could be a base64 encoded message part
+ this.dataIsBase64 = this.isBase64Encoding(data);
+ }
+
+ if (!this.dataIsBase64) {
+ if (data.search(/[\r\n][^\r\n]+[\r\n]/) >= 0) {
+ // process multi-line data line by line
+ let lines = data.replace(/\r\n/g, "\n").split(/\n/);
+
+ for (let i = 0; i < lines.length; i++) {
+ this.processData(lines[i] + "\r\n");
+ }
+ } else
+ this.processData(data);
+ } else {
+ this.base64Cache += data;
+ }
+ }
+ },
+
+ /**
+ * Try to determine if data is base64 endoded
+ */
+ isBase64Encoding: function(str) {
+ let ret = false;
+
+ str = str.replace(/[\r\n]/, "");
+ if (str.search(/^[A-Za-z0-9+/=]+$/) === 0) {
+ let excess = str.length % 4;
+ str = str.substring(0, str.length - excess);
+
+ try {
+ let s = atob(str);
+ // if the conversion succeds, we have a base64 encoded message
+ ret = true;
+ } catch (ex) {
+ // not a base64 encoded
+ }
+ }
+
+ return ret;
+ },
+
+ // cache encrypted data for writing to subprocess
+ cacheData: function(str) {
+ if (gDebugLogLevel > 4)
+ LOCAL_DEBUG("mimeDecrypt.jsm: cacheData: " + str.length + "\n");
+
+ this.outQueue += str;
+ },
+
+ processBase64Message: function() {
+ LOCAL_DEBUG("mimeDecrypt.jsm: processBase64Message\n");
+
+ try {
+ this.base64Cache = EnigmailData.decodeBase64(this.base64Cache);
+ } catch (ex) {
+ // if decoding failed, try non-encoded version
+ }
+
+ let lines = this.base64Cache.replace(/\r\n/g, "\n").split(/\n/);
+
+ for (let i = 0; i < lines.length; i++) {
+ this.processData(lines[i] + "\r\n");
+ }
+ },
+
+ /**
+ * Determine if we are reloading the same message as the previous one
+ *
+ * @return Boolean
+ */
+ isReloadingLastMessage: function() {
+ if (!this.uri) return false;
+ if (!LAST_MSG.lastMessageURI) return false;
+ if (("lastMessageData" in LAST_MSG) && LAST_MSG.lastMessageData === "") return false;
+ if (this.isUrlEnigmailConvert()) return false;
+
+ let currMsg = EnigmailURIs.msgIdentificationFromUrl(this.uri);
+
+ if (LAST_MSG.lastMessageURI.folder === currMsg.folder && LAST_MSG.lastMessageURI.msgNum === currMsg.msgNum) {
+ return true;
+ }
+
+ return false;
+ },
+
+ isUrlEnigmailConvert: function() {
+ if (!this.uri) return false;
+
+ return (this.uri.spec.search(/[&?]header=enigmailConvert/) >= 0);
+ },
+
+ onStopRequest: function(request, status, dummy) {
+ LOCAL_DEBUG("mimeDecrypt.jsm: onStopRequest\n");
+ --gNumProc;
+ if (!this.initOk) return;
+
+ if (this.dataIsBase64) {
+ this.processBase64Message();
+ }
+
+ this.msgWindow = EnigmailVerify.lastMsgWindow;
+ this.msgUriSpec = EnigmailVerify.lastMsgUri;
+
+ let url = {};
+ let currMsg = EnigmailURIs.msgIdentificationFromUrl(this.uri);
+
+ this.backgroundJob = false;
+
+ if (this.uri) {
+ // return if not decrypting currently displayed message (except if
+ // printing, replying, etc)
+
+ this.backgroundJob = (this.uri.spec.search(/[&?]header=(print|quotebody|enigmailConvert)/) >= 0);
+
+ try {
+ var messenger = Cc["@mozilla.org/messenger;1"].getService(Ci.nsIMessenger);
+
+ if (!EnigmailPrefs.getPref("autoDecrypt")) {
+ // "decrypt manually" mode
+ let manUrl = {};
+
+ if (EnigmailVerify.getManualUri()) {
+ manUrl.value = EnigmailCompat.getUrlFromUriSpec(EnigmailVerify.getManualUri());
+ } else {
+ manUrl.value = {
+ spec: "enigmail://invalid/message"
+ };
+ }
+
+ // print a message if not message explicitly decrypted
+ let currUrlSpec = this.uri.spec.replace(/(\?.*)(number=[0-9]*)(&.*)?$/, "?$2");
+ let manUrlSpec = manUrl.value.spec.replace(/(\?.*)(number=[0-9]*)(&.*)?$/, "?$2");
+
+
+ if ((!this.backgroundJob) && currUrlSpec.indexOf(manUrlSpec) !== 0) {
+ this.handleManualDecrypt();
+ return;
+ }
+ }
+
+ if (this.msgUriSpec) {
+ url.value = EnigmailCompat.getUrlFromUriSpec(this.msgUriSpec);
+ }
+
+ if (this.uri.spec.search(/[&?]header=[^&]+/) > 0 &&
+ this.uri.spec.search(/[&?]examineEncryptedParts=true/) < 0) {
+
+ if (this.uri.spec.search(/[&?]header=(filter|enigmailFilter)(&.*)?$/) > 0) {
+ EnigmailLog.DEBUG("mimeDecrypt.jsm: onStopRequest: detected incoming message processing\n");
+ return;
+ }
+ }
+
+ if (this.uri.spec.search(/[&?]header=[^&]+/) < 0 &&
+ this.uri.spec.search(/[&?]part=[.0-9]+/) < 0 &&
+ this.uri.spec.search(/[&?]examineEncryptedParts=true/) < 0) {
+
+ if (this.uri && url && url.value) {
+
+ if ("path" in url) {
+ // TB < 57
+ if (url.value.host !== this.uri.host ||
+ url.value.path !== this.uri.path)
+ return;
+ } else {
+ // TB >= 57
+ if (url.value.host !== this.uri.host ||
+ url.value.pathQueryRef !== this.uri.pathQueryRef)
+ return;
+ }
+ }
+ }
+ } catch (ex) {
+ EnigmailLog.writeException("mimeDecrypt.js", ex);
+ EnigmailLog.DEBUG("mimeDecrypt.jsm: error while processing " + this.msgUriSpec + "\n");
+ }
+ }
+
+ let spec = this.uri ? this.uri.spec : null;
+ EnigmailLog.DEBUG(`mimeDecrypt.jsm: checking MIME structure for ${this.mimePartNumber} / ${spec}\n`);
+
+ if (!EnigmailMime.isRegularMimeStructure(this.mimePartNumber, spec, false)) {
+ if (!this.isUrlEnigmailConvert()) {
+ this.returnData(EnigmailMimeDecrypt.emptyAttachment());
+ } else {
+ throw "mimeDecrypt.jsm: Cannot decrypt messages with mixed (encrypted/non-encrypted) content";
+ }
+ return;
+ }
+
+ if (!this.isReloadingLastMessage()) {
+ if (this.xferEncoding == ENCODING_BASE64) {
+ this.outQueue = EnigmailData.decodeBase64(this.outQueue) + "\n";
+ }
+
+ let win = this.msgWindow;
+
+ if (!EnigmailDecryption.isReady(win)) return;
+
+ // limit output to 100 times message size to avoid DoS attack
+ let maxOutput = this.outQueue.length * 100;
+ let statusFlagsObj = {};
+ let errorMsgObj = {};
+ let listener = this;
+
+ EnigmailLog.DEBUG("mimeDecryp.jsm: starting decryption\n");
+ EnigmailLog.DEBUG(this.outQueue + "\n");
+
+ let keyserver = EnigmailPrefs.getPref("autoKeyRetrieve");
+ let options = {
+ keyserver: keyserver,
+ keyserverProxy: EnigmailHttpProxy.getHttpProxy(keyserver),
+ fromAddr: EnigmailDecryption.getFromAddr(win),
+ maxOutputLength: maxOutput
+ };
+ const cApi = EnigmailCryptoAPI();
+ EnigmailLog.DEBUG("mimeDecrypt.jsm: got API: " + cApi.api_name + "\n");
+ this.returnStatus = cApi.sync(cApi.decryptMime(this.outQueue, options));
+ if (!this.returnStatus) {
+ this.returnStatus = {
+ decryptedData: "",
+ exitCode: -1,
+ statusFlags: EnigmailConstants.DECRYPTION_FAILED
+ };
+ }
+ this.decryptedData = this.returnStatus.decryptedData;
+ this.handleResult(this.returnStatus.exitCode);
+
+ let mdcError = ((this.returnStatus.statusFlags & EnigmailConstants.DECRYPTION_FAILED) ||
+ !(this.returnStatus.statusFlags & EnigmailConstants.DECRYPTION_OKAY));
+
+ if (!this.isUrlEnigmailConvert()) {
+ // don't return decrypted data if decryption failed (because it's likely an MDC error),
+ // unless we are called for permanent decryption
+ if (mdcError) {
+ this.decryptedData = "";
+ }
+ }
+
+ this.displayStatus();
+
+ // HACK: remove filename from 1st HTML and plaintext parts to make TB display message without attachment
+ this.decryptedData = this.decryptedData.replace(/^Content-Disposition: inline; filename="msg.txt"/m, "Content-Disposition: inline");
+ this.decryptedData = this.decryptedData.replace(/^Content-Disposition: inline; filename="msg.html"/m, "Content-Disposition: inline");
+
+ let prefix = EnigmailMimeDecrypt.pretendAttachment(this.mimePartNumber, this.uri);
+ this.returnData(prefix + this.decryptedData);
+
+ // don't remember the last message if it contains an embedded PGP/MIME message
+ // to avoid ending up in a loop
+ if (this.mimePartNumber === "1" &&
+ this.decryptedData.search(/^Content-Type:[\t ]+multipart\/encrypted/mi) < 0 &&
+ !mdcError) {
+ LAST_MSG.lastMessageData = this.decryptedData;
+ LAST_MSG.lastMessageURI = currMsg;
+ LAST_MSG.lastStatus = this.returnStatus;
+ LAST_MSG.lastStatus.decryptedHeaders = this.decryptedHeaders;
+ LAST_MSG.lastStatus.mimePartNumber = this.mimePartNumber;
+ } else {
+ LAST_MSG.lastMessageURI = null;
+ LAST_MSG.lastMessageData = "";
+ }
+
+ this.decryptedData = "";
+ EnigmailLog.DEBUG("mimeDecrypt.jsm: onStopRequest: process terminated\n"); // always log this one
+ this.proc = null;
+ } else {
+ this.returnStatus = LAST_MSG.lastStatus;
+ this.decryptedHeaders = LAST_MSG.lastStatus.decryptedHeaders;
+ this.mimePartNumber = LAST_MSG.lastStatus.mimePartNumber;
+ this.exitCode = 0;
+ this.displayStatus();
+ this.returnData(LAST_MSG.lastMessageData);
+ }
+ },
+
+ displayStatus: function() {
+ EnigmailLog.DEBUG("mimeDecrypt.jsm: displayStatus()\n");
+
+ if (this.exitCode === null || this.msgWindow === null || this.statusDisplayed) {
+ EnigmailLog.DEBUG("mimeDecrypt.jsm: displayStatus: nothing to display\n");
+ return;
+ }
+
+ let uriSpec = (this.uri ? this.uri.spec : null);
+
+ try {
+ EnigmailLog.DEBUG("mimeDecrypt.jsm: displayStatus for uri " + uriSpec + "\n");
+ let headerSink = EnigmailSingletons.messageReader;
+
+ if (headerSink && this.uri && !this.backgroundJob) {
+
+ headerSink.processDecryptionResult(this.uri, "modifyMessageHeaders", JSON.stringify(this.decryptedHeaders), this.mimePartNumber);
+
+ headerSink.updateSecurityStatus(
+ this.msgUriSpec,
+ this.exitCode,
+ this.returnStatus.statusFlags,
+ this.returnStatus.keyId,
+ this.returnStatus.userId,
+ this.returnStatus.sigDetails,
+ this.returnStatus.errorMsg,
+ this.returnStatus.blockSeparation,
+ this.uri,
+ JSON.stringify({
+ encryptedTo: this.returnStatus.encToDetails
+ }),
+ this.mimePartNumber);
+ } else {
+ this.updateHeadersInMsgDb();
+ }
+ this.statusDisplayed = true;
+ } catch (ex) {
+ EnigmailLog.writeException("mimeDecrypt.jsm", ex);
+ }
+ LOCAL_DEBUG("mimeDecrypt.jsm: displayStatus done\n");
+ },
+
+ handleResult: function(exitCode) {
+ LOCAL_DEBUG("mimeDecrypt.jsm: done: " + exitCode + "\n");
+
+ if (gDebugLogLevel > 4)
+ LOCAL_DEBUG("mimeDecrypt.jsm: done: decrypted data='" + this.decryptedData + "'\n");
+
+ // ensure newline at the end of the stream
+ if (!this.decryptedData.endsWith("\n")) {
+ this.decryptedData += "\r\n";
+ }
+
+ try {
+ this.extractEncryptedHeaders();
+ this.extractAutocryptGossip();
+ } catch (ex) {}
+
+ let i = this.decryptedData.search(/\n\r?\n/);
+ if (i > 0) {
+ var hdr = this.decryptedData.substr(0, i).split(/\r?\n/);
+ for (let j = 0; j < hdr.length; j++) {
+ if (hdr[j].search(/^\s*content-type:\s+text\/(plain|html)/i) >= 0) {
+ LOCAL_DEBUG("mimeDecrypt.jsm: done: adding multipart/mixed around " + hdr[j] + "\n");
+
+ this.addWrapperToDecryptedResult();
+ break;
+ }
+ }
+ }
+
+ this.exitCode = exitCode;
+ },
+
+ addWrapperToDecryptedResult: function() {
+ if (!this.isUrlEnigmailConvert()) {
+ let wrapper = EnigmailMime.createBoundary();
+
+ this.decryptedData = 'Content-Type: multipart/mixed; boundary="' + wrapper + '"\r\n' +
+ 'Content-Disposition: inline\r\n\r\n' +
+ '--' + wrapper + '\r\n' +
+ this.decryptedData + '\r\n' +
+ '--' + wrapper + '--\r\n';
+ }
+ },
+
+ extractContentType: function(data) {
+ let i = data.search(/\n\r?\n/);
+ if (i <= 0) return null;
+
+ let headers = Cc["@mozilla.org/messenger/mimeheaders;1"].createInstance(Ci.nsIMimeHeaders);
+ headers.initialize(data.substr(0, i));
+ return headers.extractHeader("content-type", false);
+ },
+
+ // return data to libMime
+ returnData: function(data) {
+ EnigmailLog.DEBUG("mimeDecrypt.jsm: returnData: " + data.length + " bytes\n");
+
+ let proto = null;
+ let ct = this.extractContentType(data);
+ if (ct && ct.search(/multipart\/signed/i) >= 0) {
+ proto = EnigmailMime.getProtocol(ct);
+ }
+
+ try {
+ if (proto && proto.search(/application\/(pgp|pkcs7|x-pkcs7)-signature/i) >= 0) {
+ EnigmailLog.DEBUG("mimeDecrypt.jsm: returnData: using direct verification\n");
+ this.mimeSvc.contentType = ct;
+ if ("mimePart" in this.mimeSvc) {
+ this.mimeSvc.mimePart = this.mimeSvc.mimePart + ".1";
+ }
+ let veri = EnigmailVerify.newVerifier(proto);
+ veri.onStartRequest(this.mimeSvc, this.uri);
+ veri.onTextData(data);
+ veri.onStopRequest(null, 0);
+ } else {
+ if ("outputDecryptedData" in this.mimeSvc) {
+ // TB >= 57
+ this.mimeSvc.outputDecryptedData(data, data.length);
+ } else {
+ let gConv = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
+ gConv.setData(data, data.length);
+ this.mimeSvc.onStartRequest(null, null);
+ this.mimeSvc.onDataAvailable(null, null, gConv, 0, data.length);
+ this.mimeSvc.onStopRequest(null, null, 0);
+ }
+ }
+ } catch (ex) {
+ EnigmailLog.ERROR("mimeDecrypt.jsm: returnData(): mimeSvc.onDataAvailable failed:\n" + ex.toString());
+ }
+ },
+
+ handleManualDecrypt: function() {
+
+ try {
+ let headerSink = EnigmailSingletons.messageReader;
+
+ if (headerSink && this.uri && !this.backgroundJob) {
+ headerSink.updateSecurityStatus(
+ this.msgUriSpec,
+ EnigmailConstants.POSSIBLE_PGPMIME,
+ 0,
+ "",
+ "",
+ "",
+ EnigmailLocale.getString("possiblyPgpMime"),
+ "",
+ this.uri,
+ null,
+ "");
+ }
+ } catch (ex) {}
+
+ return 0;
+ },
+
+ updateHeadersInMsgDb: function() {
+ if (this.mimePartNumber !== "1") return;
+ if (!this.uri) return;
+
+ if (this.decryptedHeaders && ("subject" in this.decryptedHeaders)) {
+ try {
+ let msgDbHdr = this.uri.QueryInterface(Ci.nsIMsgMessageUrl).messageHeader;
+ msgDbHdr.subject = EnigmailData.convertFromUnicode(this.decryptedHeaders.subject, "utf-8");
+ } catch (x) {}
+ }
+ },
+
+ extractEncryptedHeaders: function() {
+ let r = EnigmailMime.extractProtectedHeaders(this.decryptedData);
+ if (!r) return;
+
+ this.decryptedHeaders = r.newHeaders;
+ if (r.startPos >= 0 && r.endPos > r.startPos) {
+ this.decryptedData = this.decryptedData.substr(0, r.startPos) + this.decryptedData.substr(r.endPos);
+ }
+ },
+
+ extractAutocryptGossip: async function() {
+ let m1 = this.decryptedData.search(/^--/m);
+ let m2 = this.decryptedData.search(/\r?\n\r?\n/);
+ let m = Math.max(m1, m2);
+
+ let hdr = Cc["@mozilla.org/messenger/mimeheaders;1"].createInstance(Ci.nsIMimeHeaders);
+ hdr.initialize(this.decryptedData.substr(0, m));
+
+ let gossip = hdr.getHeader("autocrypt-gossip") || [];
+ EnigmailLog.DEBUG(`mimeDecrypt.jsm: extractAutocryptGossip: found ${gossip.length} headers\n`);
+
+ let msgDate = null;
+ try {
+ msgDate = this.uri.QueryInterface(Ci.nsIMsgMessageUrl).messageHeader.dateInSeconds;
+ } catch (x) {}
+
+
+ for (let i in gossip) {
+ let addr = EnigmailMime.getParameter(gossip[i], "addr");
+ try {
+ let r = await EnigmailAutocrypt.processAutocryptHeader(addr, [gossip[i].replace(/ /g, "")], msgDate, true, true);
+ EnigmailLog.DEBUG(`mimeDecrypt.jsm: extractAutocryptGossip: r=${r}\n`);
+ } catch (x) {
+ EnigmailLog.DEBUG(`mimeDecrypt.jsm: extractAutocryptGossip: Error: ${x}\n`);
+ }
+ }
+ }
+};
+
+
+////////////////////////////////////////////////////////////////////
+// General-purpose functions, not exported
+
+function LOCAL_DEBUG(str) {
+ if (gDebugLogLevel) EnigmailLog.DEBUG(str);
+}
+
+function initModule() {
+ var env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
+ var nspr_log_modules = env.get("NSPR_LOG_MODULES");
+ var matches = nspr_log_modules.match(/mimeDecrypt:(\d+)/);
+
+ if (matches && (matches.length > 1)) {
+ gDebugLogLevel = matches[1];
+ }
+}
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/mimeEncrypt.jsm
@@ -0,0 +1,756 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * Module for creating PGP/MIME signed and/or encrypted messages
+ * implemented as XPCOM component
+ */
+
+var EXPORTED_SYMBOLS = ["EnigmailMimeEncrypt"];
+
+const Cr = Components.results;
+
+const jsmime = ChromeUtils.import("resource:///modules/jsmime.jsm").jsmime;
+const EnigmailCompat = ChromeUtils.import("chrome://openpgp/content/modules/compat.jsm").EnigmailCompat;
+const EnigmailFuncs = ChromeUtils.import("chrome://openpgp/content/modules/funcs.jsm").EnigmailFuncs;
+const EnigmailDialog = ChromeUtils.import("chrome://openpgp/content/modules/dialog.jsm").EnigmailDialog;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailEncryption = ChromeUtils.import("chrome://openpgp/content/modules/encryption.jsm").EnigmailEncryption;
+const EnigmailMime = ChromeUtils.import("chrome://openpgp/content/modules/mime.jsm").EnigmailMime;
+const EnigmailHash = ChromeUtils.import("chrome://openpgp/content/modules/hash.jsm").EnigmailHash;
+const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
+const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
+const EnigmailKeyRing = ChromeUtils.import("chrome://openpgp/content/modules/keyRing.jsm").EnigmailKeyRing;
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+
+// our own contract IDs
+const PGPMIME_ENCRYPT_CID = Components.ID("{96fe88f9-d2cd-466f-93e0-3a351df4c6d2}");
+const PGPMIME_ENCRYPT_CONTRACTID = "@enigmail.net/compose/mimeencrypt;1";
+
+const APPSHELL_MEDIATOR_CONTRACTID = "@mozilla.org/appshell/window-mediator;1";
+
+// S/MIME contract IDs
+const SMIME_ENCRYPT_CONTRACTID = "@mozilla.org/messengercompose/composesecure;1";
+const kSmimeComposeSecureCID = "{dd753201-9a23-4e08-957f-b3616bf7e012}";
+
+const maxBufferLen = 102400;
+const MIME_SIGNED = 1;
+const MIME_ENCRYPTED = 2;
+
+var gDebugLogLevel = 0;
+
+function PgpMimeEncrypt(sMimeSecurityInfo) {
+ this.wrappedJSObject = this;
+
+ // nsIMsgSMIMECompFields
+ this.signMessage = false;
+ this.requireEncryptMessage = false;
+
+ // "securityInfo" variables
+ this.sendFlags = 0;
+ this.UIFlags = 0;
+ this.senderEmailAddr = "";
+ this.recipients = "";
+ this.bccRecipients = "";
+ this.originalSubject = null;
+ this.keyMap = {};
+
+ if (EnigmailCompat.isMessageUriInPgpMime()) {
+ this.onDataAvailable = this.onDataAvailable68;
+ }
+ else {
+ this.onDataAvailable = this.onDataAvailable60;
+ }
+
+ try {
+ if (sMimeSecurityInfo) {
+ if ("nsIMsgSMIMECompFields" in Ci) {
+ sMimeSecurityInfo = sMimeSecurityInfo.QueryInterface(Ci.nsIMsgSMIMECompFields);
+ }
+ this.signMessage = sMimeSecurityInfo.signMessage;
+ this.requireEncryptMessage = sMimeSecurityInfo.requireEncryptMessage;
+ }
+ }
+ catch (ex) {}
+}
+
+PgpMimeEncrypt.prototype = {
+ classDescription: "Enigmail JS Encryption Handler",
+ classID: PGPMIME_ENCRYPT_CID,
+ get contractID() {
+ if (Components.classesByID && Components.classesByID[kSmimeComposeSecureCID]) {
+ // hack needed for TB < 62: we overwrite the S/MIME encryption handler
+ return SMIME_ENCRYPT_CONTRACTID;
+ }
+ else {
+ return PGPMIME_ENCRYPT_CONTRACTID;
+ }
+ },
+ QueryInterface: EnigmailCompat.generateQI([
+ "nsIMsgComposeSecure",
+ "nsIStreamListener",
+ "nsIMsgSMIMECompFields" // TB < 64
+ ]),
+
+ signMessage: false,
+ requireEncryptMessage: false,
+
+ // private variables
+
+ inStream: Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream),
+ msgCompFields: null,
+ smimeCompose: null,
+ useSmime: false,
+ outStringStream: null,
+
+ // 0: processing headers
+ // 1: processing body
+ // 2: skipping header
+ inputMode: 0,
+ dataLength: 0,
+ headerData: "",
+ encapsulate: null,
+ encHeader: null,
+ cryptoBoundary: null,
+ win: null,
+ pipe: null,
+ proc: null,
+ statusStr: "",
+ encryptedData: "",
+ hashAlgorithm: null,
+ pipeQueue: "",
+ outQueue: "",
+ closePipe: false,
+ cryptoMode: 0,
+ exitCode: -1,
+ inspector: null,
+ checkSMime: true,
+
+ // nsIStreamListener interface
+ onStartRequest: function(request) {
+ EnigmailLog.DEBUG("mimeEncrypt.js: onStartRequest\n");
+ this.encHeader = null;
+ },
+
+ /**
+ * onDataAvailable for TB <= 66
+ */
+ onDataAvailable60: function(req, ctxt, stream, offset, count) {
+ LOCAL_DEBUG("mimeEncrypt.js: onDataAvailable\n");
+ this.inStream.init(stream);
+ var data = this.inStream.read(count);
+ //LOCAL_DEBUG("mimeEncrypt.js: >"+data+"<\n");
+
+ },
+
+ /**
+ * onDataAvailable for TB >= 67
+ */
+ onDataAvailable68: function(req, stream, offset, count) {
+ LOCAL_DEBUG("mimeEncrypt.js: onDataAvailable\n");
+ this.inStream.init(stream);
+ var data = this.inStream.read(count);
+ //LOCAL_DEBUG("mimeEncrypt.js: >"+data+"<\n");
+
+ },
+
+ onStopRequest: function(request, status) {
+ EnigmailLog.DEBUG("mimeEncrypt.js: onStopRequest\n");
+ },
+
+ disableSMimeCheck: function() {
+ this.useSmime = false;
+ this.checkSMime = false;
+ },
+
+ // nsIMsgComposeSecure interface
+ requiresCryptoEncapsulation: function(msgIdentity, msgCompFields) {
+ EnigmailLog.DEBUG("mimeEncrypt.js: requiresCryptoEncapsulation\n");
+ try {
+ if (Components.classesByID && kSmimeComposeSecureCID in Components.classesByID) {
+ // TB < 64
+ if (this.checkSMime) {
+ // Remember to use original CID, not CONTRACTID, to avoid infinite looping!
+ this.smimeCompose = Components.classesByID[kSmimeComposeSecureCID].createInstance(Ci.nsIMsgComposeSecure);
+ this.useSmime = this.smimeCompose.requiresCryptoEncapsulation(msgIdentity, msgCompFields);
+ }
+
+ if (this.useSmime) return true;
+
+ if (msgCompFields.securityInfo) {
+ let securityInfo = msgCompFields.securityInfo.wrappedJSObject;
+ if (!securityInfo) return false;
+
+ for (let prop of ["sendFlags", "UIFlags", "senderEmailAddr", "recipients", "bccRecipients", "originalSubject", "keyMap"]) {
+ this[prop] = securityInfo[prop];
+ }
+ }
+ else return false;
+ }
+ else {
+ // TB >= 64: we are not called for S/MIME
+ this.disableSMimeCheck();
+ }
+
+ return (this.sendFlags & (EnigmailConstants.SEND_SIGNED |
+ EnigmailConstants.SEND_ENCRYPTED |
+ EnigmailConstants.SEND_VERBATIM)) !== 0;
+ }
+ catch (ex) {
+ EnigmailLog.writeException("mimeEncrypt.js", ex);
+ throw (ex);
+ }
+ },
+
+ beginCryptoEncapsulation: function(outStream, recipientList, msgCompFields, msgIdentity, sendReport, isDraft) {
+ EnigmailLog.DEBUG("mimeEncrypt.js: beginCryptoEncapsulation\n");
+
+ if (this.checkSMime && (!this.smimeCompose)) {
+ LOCAL_DEBUG("mimeEncrypt.js: beginCryptoEncapsulation: ERROR MsgComposeSecure not instantiated\n");
+ throw Cr.NS_ERROR_FAILURE;
+ }
+
+ if (this.useSmime)
+ return this.smimeCompose.beginCryptoEncapsulation(outStream, recipientList,
+ msgCompFields, msgIdentity,
+ sendReport, isDraft);
+
+ if (!outStream) throw Cr.NS_ERROR_NULL_POINTER;
+
+ try {
+
+ this.outStream = outStream;
+ this.isDraft = isDraft;
+
+ this.msgCompFields = msgCompFields;
+ this.outStringStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
+
+ var windowManager = Cc[APPSHELL_MEDIATOR_CONTRACTID].getService(Ci.nsIWindowMediator);
+ this.win = windowManager.getMostRecentWindow(null);
+
+ if (this.sendFlags & EnigmailConstants.SEND_VERBATIM) {
+ this.recipientList = recipientList;
+ this.msgIdentity = msgIdentity;
+ this.msgCompFields = msgCompFields;
+ this.inputMode = 2;
+ return null;
+ }
+
+ if (this.sendFlags & EnigmailConstants.SEND_PGP_MIME) {
+
+ if (this.sendFlags & EnigmailConstants.SEND_ENCRYPTED) {
+ // applies to encrypted and signed & encrypted
+ this.cryptoMode = MIME_ENCRYPTED;
+ }
+ else if (this.sendFlags & EnigmailConstants.SEND_SIGNED) {
+ this.cryptoMode = MIME_SIGNED;
+
+ let hashAlgoObj = {};
+ if (EnigmailHash.determineAlgorithm(this.win,
+ this.UIFlags,
+ this.senderEmailAddr,
+ hashAlgoObj) === 0) {
+ this.hashAlgorithm = hashAlgoObj.value;
+ }
+ else {
+ if ("statusFlags" in hashAlgoObj && hashAlgoObj.statusFlags !== 0 && hashAlgoObj.errorMsg) {
+ EnigmailDialog.alert(this.win, hashAlgoObj.errorMsg);
+ }
+
+ throw Cr.NS_ERROR_FAILURE;
+ }
+ }
+ }
+ else
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+
+ this.cryptoBoundary = EnigmailMime.createBoundary();
+ this.startCryptoHeaders();
+
+ }
+ catch (ex) {
+ EnigmailLog.writeException("mimeEncrypt.js", ex);
+ throw (ex);
+ }
+
+ return null;
+ },
+
+ startCryptoHeaders: function() {
+ EnigmailLog.DEBUG("mimeEncrypt.js: startCryptoHeaders\n");
+
+ if (this.cryptoMode == MIME_SIGNED) this.signedHeaders1(false);
+ if (this.cryptoMode == MIME_ENCRYPTED) this.encryptedHeaders();
+
+ this.writeSecureHeaders();
+ },
+
+ writeSecureHeaders: function() {
+ this.encHeader = EnigmailMime.createBoundary();
+
+ let allHdr = "";
+
+ if (this.sendFlags & EnigmailConstants.ENCRYPT_HEADERS) {
+ let addrParser = jsmime.headerparser.parseAddressingHeader;
+ let newsParser = function(s) {
+ return jsmime.headerparser.parseStructuredHeader("Newsgroups", s);
+ };
+ let noParser = function(s) {
+ return s;
+ };
+
+ let h = {
+ from: {
+ field: "From",
+ parser: addrParser
+ },
+ replyTo: {
+ field: "Reply-To",
+ parser: addrParser
+ },
+ to: {
+ field: "To",
+ parser: addrParser
+ },
+ cc: {
+ field: "Cc",
+ parser: addrParser
+ },
+ newsgroups: {
+ field: "Newsgroups",
+ parser: newsParser
+ },
+ followupTo: {
+ field: "Followup-To",
+ parser: addrParser
+ },
+ messageId: {
+ field: "Message-Id",
+ parser: noParser
+ },
+ subject: {
+ field: "Subject",
+ parser: noParser
+ }
+ };
+
+ for (let i in h) {
+ if (this.msgCompFields[i] && this.msgCompFields[i].length > 0) {
+ allHdr += jsmime.headeremitter.emitStructuredHeader(h[i].field, h[i].parser(this.msgCompFields[i]), {});
+ }
+ }
+
+ if (this.cryptoMode == MIME_ENCRYPTED && this.originalSubject && this.originalSubject.length > 0) {
+ allHdr += jsmime.headeremitter.emitStructuredHeader("subject", this.originalSubject, {});
+ }
+
+ // special handling for references and in-reply-to
+
+ if (this.originalReferences && this.originalReferences.length > 0) {
+ allHdr += jsmime.headeremitter.emitStructuredHeader("references", this.originalReferences, {});
+
+ let bracket = this.originalReferences.lastIndexOf("<");
+ if (bracket >= 0) {
+ allHdr += jsmime.headeremitter.emitStructuredHeader("in-reply-to", this.originalReferences.substr(bracket), {});
+ }
+ }
+ }
+
+ let w = `Content-Type: multipart/mixed; boundary="${this.encHeader}"`;
+
+ if (allHdr.length > 0) {
+ w += `;\r\n protected-headers="v1"\r\n${allHdr}`;
+ }
+ else {
+ w += '\r\n';
+ }
+
+ w += this.getAutocryptGossip() + `\r\n--${this.encHeader}\r\n`;
+ this.writeToPipe(w);
+
+ if (this.cryptoMode == MIME_SIGNED) this.writeOut(w);
+ },
+
+ getAutocryptGossip: function() {
+ let gossip = "";
+ if (this.cryptoMode == MIME_ENCRYPTED &&
+ this.msgCompFields.hasHeader("autocrypt") &&
+ this.keyMap &&
+ EnigmailFuncs.getNumberOfRecipients(this.msgCompFields) > 1) {
+ for (let email in this.keyMap) {
+ let keyObj = EnigmailKeyRing.getKeyById(this.keyMap[email]);
+ if (keyObj) {
+ let k = keyObj.getMinimalPubKey(email);
+ if (k.exitCode === 0) {
+ let keyData = " " + k.keyData.replace(/(.{72})/g, "$1\r\n ").replace(/\r\n $/, "");
+ gossip += 'Autocrypt-Gossip: addr=' + email + '; keydata=\r\n' + keyData + "\r\n";
+ }
+ }
+ }
+ }
+
+ return gossip;
+ },
+
+ encryptedHeaders: function(isEightBit) {
+ EnigmailLog.DEBUG("mimeEncrypt.js: encryptedHeaders\n");
+ let subj = "";
+
+ if (this.sendFlags & EnigmailConstants.ENCRYPT_HEADERS) {
+ subj = jsmime.headeremitter.emitStructuredHeader("subject", EnigmailFuncs.getProtectedSubjectText(), {});
+ }
+
+ this.writeOut(subj +
+ "Content-Type: multipart/encrypted;\r\n" +
+ " protocol=\"application/pgp-encrypted\";\r\n" +
+ " boundary=\"" + this.cryptoBoundary + "\"\r\n" +
+ "\r\n" +
+ "This is an OpenPGP/MIME encrypted message (RFC 4880 and 3156)\r\n" +
+ "--" + this.cryptoBoundary + "\r\n" +
+ "Content-Type: application/pgp-encrypted\r\n" +
+ "Content-Description: PGP/MIME version identification\r\n" +
+ "\r\n" +
+ "Version: 1\r\n" +
+ "\r\n" +
+ "--" + this.cryptoBoundary + "\r\n" +
+ "Content-Type: application/octet-stream; name=\"encrypted.asc\"\r\n" +
+ "Content-Description: OpenPGP encrypted message\r\n" +
+ "Content-Disposition: inline; filename=\"encrypted.asc\"\r\n" +
+ "\r\n");
+ },
+
+ signedHeaders1: function(isEightBit) {
+ LOCAL_DEBUG("mimeEncrypt.js: signedHeaders1\n");
+ this.writeOut("Content-Type: multipart/signed; micalg=pgp-" +
+ this.hashAlgorithm.toLowerCase() +
+ ";\r\n" +
+ " protocol=\"application/pgp-signature\";\r\n" +
+ " boundary=\"" + this.cryptoBoundary + "\"\r\n" +
+ (isEightBit ? "Content-Transfer-Encoding: 8bit\r\n\r\n" : "\r\n") +
+ "This is an OpenPGP/MIME signed message (RFC 4880 and 3156)\r\n" +
+ "--" + this.cryptoBoundary + "\r\n");
+ },
+
+
+ signedHeaders2: function() {
+ LOCAL_DEBUG("mimeEncrypt.js: signedHeaders2\n");
+
+ this.writeOut("\r\n--" + this.cryptoBoundary + "\r\n" +
+ "Content-Type: application/pgp-signature; name=\"signature.asc\"\r\n" +
+ "Content-Description: OpenPGP digital signature\r\n" +
+ "Content-Disposition: attachment; filename=\"signature.asc\"\r\n\r\n");
+ },
+
+ finishCryptoHeaders: function() {
+ EnigmailLog.DEBUG("mimeEncrypt.js: finishCryptoHeaders\n");
+
+ this.writeOut("\r\n--" + this.cryptoBoundary + "--\r\n");
+ },
+
+ finishCryptoEncapsulation: function(abort, sendReport) {
+ EnigmailLog.DEBUG("mimeEncrypt.js: finishCryptoEncapsulation\n");
+
+ if (this.checkSMime && (!this.smimeCompose))
+ throw Cr.NS_ERROR_NOT_INITIALIZED;
+
+ if (this.useSmime) {
+ this.smimeCompose.finishCryptoEncapsulation(abort, sendReport);
+ return;
+ }
+
+ if ((this.sendFlags & EnigmailConstants.SEND_VERBATIM) !== 0) {
+ this.flushOutput();
+ return;
+ }
+
+
+ let statusFlagsObj = {};
+ let errorMsgObj = {};
+ let proc = EnigmailEncryption.encryptMessageStart(this.win,
+ this.UIFlags,
+ this.senderEmailAddr,
+ this.recipients,
+ this.bccRecipients,
+ this.hashAlgorithm,
+ this.sendFlags,
+ this,
+ statusFlagsObj,
+ errorMsgObj);
+ if (!proc) throw Cr.NS_ERROR_FAILURE;
+
+ try {
+ if (this.encapsulate) this.writeToPipe("--" + this.encapsulate + "--\r\n");
+
+ if (this.encHeader) {
+ this.writeToPipe("\r\n--" + this.encHeader + "--\r\n");
+ if (this.cryptoMode == MIME_SIGNED) this.writeOut("\r\n--" + this.encHeader + "--\r\n");
+ }
+
+ this.flushInput();
+
+ if (!this.pipe) {
+ this.closePipe = true;
+ }
+ else
+ this.pipe.close();
+
+ // wait here for proc to terminate
+ proc.wait();
+
+ LOCAL_DEBUG("mimeEncrypt.js: finishCryptoEncapsulation: exitCode = " + this.exitCode + "\n");
+ if (this.exitCode !== 0) throw Cr.NS_ERROR_FAILURE;
+
+ if (this.cryptoMode == MIME_SIGNED) this.signedHeaders2();
+
+ this.encryptedData = this.encryptedData.replace(/\r/g, "").replace(/\n/g, "\r\n"); // force CRLF
+ this.writeOut(this.encryptedData);
+ this.finishCryptoHeaders();
+ this.flushOutput();
+ }
+ catch (ex) {
+ EnigmailLog.writeException("mimeEncrypt.js", ex);
+ throw (ex);
+ }
+
+ },
+
+ mimeCryptoWriteBlock: function(buffer, length) {
+ if (gDebugLogLevel > 4)
+ LOCAL_DEBUG("mimeEncrypt.js: mimeCryptoWriteBlock: " + length + "\n");
+
+ if (this.checkSMime && (!this.smimeCompose))
+ throw Cr.NS_ERROR_NOT_INITIALIZED;
+
+ if (this.useSmime) return this.smimeCompose.mimeCryptoWriteBlock(buffer, length);
+
+ try {
+ let line = buffer.substr(0, length);
+ if (this.inputMode === 0) {
+ if ((this.sendFlags & EnigmailConstants.SEND_VERBATIM) !== 0) {
+ line = EnigmailData.decodeQuotedPrintable(line.replace("=\r\n", ""));
+ }
+
+ if ((this.sendFlags & EnigmailConstants.SEND_VERBATIM) === 0 ||
+ line.match(/^(From|To|Subject|Message-ID|Date|User-Agent|MIME-Version):/i) === null) {
+ this.headerData += line;
+ }
+
+ if (line.replace(/[\r\n]/g, "").length === 0) {
+ this.inputMode = 1;
+
+ if (this.cryptoMode == MIME_ENCRYPTED) {
+ if (!this.encHeader) {
+ let ct = this.getHeader("content-type", false);
+ if ((ct.search(/text\/plain/i) === 0) || (ct.search(/text\/html/i) === 0)) {
+ this.encapsulate = EnigmailMime.createBoundary();
+ this.writeToPipe('Content-Type: multipart/mixed; boundary="' +
+ this.encapsulate + '"\r\n\r\n');
+ this.writeToPipe("--" + this.encapsulate + "\r\n");
+ }
+ }
+ }
+ else if (this.cryptoMode == MIME_SIGNED) {
+ let ct = this.getHeader("content-type", true);
+ let hdr = EnigmailFuncs.getHeaderData(ct);
+ hdr.boundary = hdr.boundary || "";
+ hdr.boundary = hdr.boundary.replace(/['"]/g, "");
+ }
+
+ this.writeToPipe(this.headerData);
+ if (this.cryptoMode == MIME_SIGNED ||
+ (this.sendFlags & EnigmailConstants.SEND_VERBATIM) !== 0) {
+ this.writeOut(this.headerData);
+ }
+ }
+
+ }
+ else if (this.inputMode == 1) {
+ if (this.cryptoMode == MIME_SIGNED) {
+ // special treatments for various special cases with PGP/MIME signed messages
+ if (line.substr(0, 5) == "From ") {
+ LOCAL_DEBUG("mimeEncrypt.js: added >From\n");
+ this.writeToPipe(">");
+ }
+ }
+
+ this.writeToPipe(line);
+ if (this.cryptoMode == MIME_SIGNED) {
+ this.writeOut(line);
+ }
+ else if ((this.sendFlags & EnigmailConstants.SEND_VERBATIM) !== 0) {
+ this.writeOut(EnigmailData.decodeQuotedPrintable(line.replace("=\r\n", "")));
+ }
+ }
+ else if (this.inputMode == 2) {
+ if (line.replace(/[\r\n]/g, "").length === 0) {
+ this.inputMode = 0;
+ }
+ }
+ }
+ catch (ex) {
+ EnigmailLog.writeException("mimeEncrypt.js", ex);
+ throw (ex);
+ }
+
+ return null;
+ },
+
+ writeOut: function(str) {
+ if (gDebugLogLevel > 4)
+ LOCAL_DEBUG("mimeEncrypt.js: writeOut: " + str.length + "\n");
+
+ this.outQueue += str;
+
+ if (this.outQueue.length > maxBufferLen)
+ this.flushOutput();
+ },
+
+ flushOutput: function() {
+ LOCAL_DEBUG("mimeEncrypt.js: flushOutput: " + this.outQueue.length + "\n");
+
+ this.outStringStream.setData(this.outQueue, this.outQueue.length);
+ var writeCount = this.outStream.writeFrom(this.outStringStream, this.outQueue.length);
+ if (writeCount < this.outQueue.length) {
+ LOCAL_DEBUG("mimeEncrypt.js: flushOutput: wrote " + writeCount + " instead of " + this.outQueue.length + " bytes\n");
+ }
+ this.outQueue = "";
+ },
+
+ writeToPipe: function(str) {
+ if (gDebugLogLevel > 4)
+ LOCAL_DEBUG("mimeEncrypt.js: writeToPipe: " + str.length + "\n");
+
+ if (this.pipe) {
+ this.pipeQueue += str;
+ if (this.pipeQueue.length > maxBufferLen)
+ this.flushInput();
+ }
+ else
+ this.pipeQueue += str;
+ },
+
+ flushInput: function() {
+ LOCAL_DEBUG("mimeEncrypt.js: flushInput\n");
+ if (!this.pipe) return;
+ this.pipe.write(this.pipeQueue);
+ this.pipeQueue = "";
+ },
+
+ getHeader: function(hdrStr, fullHeader) {
+ var foundIndex = 0;
+ var res = "";
+ var hdrLines = this.headerData.split(/[\r\n]+/);
+ var i;
+ for (i = 0; i < hdrLines.length; i++) {
+ if (hdrLines[i].length > 0) {
+ if (fullHeader && res !== "") {
+ if (hdrLines[i].search(/^\s+/) === 0) {
+ res += hdrLines[i].replace(/\s*[\r\n]*$/, "");
+ }
+ else
+ return res;
+ }
+ else {
+ let j = hdrLines[i].indexOf(":");
+ if (j > 0) {
+ let h = hdrLines[i].substr(0, j).replace(/\s*$/, "");
+ let re = new RegExp("^" + hdrStr + "$", "i");
+ if (h.search(re) === 0) {
+ foundIndex = 1;
+ res = hdrLines[i].substr(j + 1).replace(/^\s*/, "");
+ if (!fullHeader) return res;
+ }
+ }
+ }
+ }
+ }
+
+ return res;
+ },
+
+
+ // API for decryptMessage Listener
+ stdin: function(pipe) {
+ LOCAL_DEBUG("mimeEncrypt.js: stdin\n");
+ if (this.pipeQueue.length > 0) {
+ pipe.write(this.pipeQueue);
+ this.pipeQueue = "";
+ }
+ if (this.closePipe) {
+ pipe.close();
+ }
+ else {
+ this.pipe = pipe;
+ }
+ },
+
+ stdout: function(s) {
+ LOCAL_DEBUG("mimeEncrypt.js: stdout:" + s.length + "\n");
+ this.encryptedData += s;
+ this.dataLength += s.length;
+ },
+
+ stderr: function(s) {
+ LOCAL_DEBUG("mimeEncrypt.js: stderr\n");
+ this.statusStr += s;
+ },
+
+ done: function(exitCode) {
+ EnigmailLog.DEBUG("mimeEncrypt.js: done: " + exitCode + "\n");
+
+ let retStatusObj = {};
+
+ this.exitCode = EnigmailEncryption.encryptMessageEnd(this.senderEmailAddr,
+ this.statusStr,
+ exitCode,
+ this.UIFlags,
+ this.sendFlags,
+ this.dataLength,
+ retStatusObj);
+
+ if (this.exitCode !== 0)
+ EnigmailDialog.alert(this.win, retStatusObj.errorMsg);
+
+ },
+};
+
+
+////////////////////////////////////////////////////////////////////
+// General-purpose functions, not exported
+
+
+function LOCAL_DEBUG(str) {
+ if (gDebugLogLevel) EnigmailLog.DEBUG(str);
+}
+
+function initModule() {
+ EnigmailLog.DEBUG("mimeEncrypt.jsm: initModule()\n");
+ var env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
+ var nspr_log_modules = env.get("NSPR_LOG_MODULES");
+ var matches = nspr_log_modules.match(/mimeEncrypt:(\d+)/);
+
+ if (matches && (matches.length > 1)) {
+ gDebugLogLevel = matches[1];
+ LOCAL_DEBUG("mimeEncrypt.js: enabled debug logging\n");
+ }
+}
+
+var EnigmailMimeEncrypt = {
+ Handler: PgpMimeEncrypt,
+
+ startup: function(reason) {
+ initModule();
+ },
+ shutdown: function(reason) {},
+
+ createMimeEncrypt: function(sMimeSecurityInfo) {
+ return new PgpMimeEncrypt(sMimeSecurityInfo);
+ },
+
+ isEnigmailCompField: function(obj) {
+ return obj instanceof PgpMimeEncrypt;
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/mimeVerify.jsm
@@ -0,0 +1,614 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailVerify"];
+
+/**
+ * Module for handling PGP/MIME signed messages
+ * implemented as JS module
+ */
+
+const EnigmailCompat = ChromeUtils.import("chrome://openpgp/content/modules/compat.jsm").EnigmailCompat;
+const EnigmailFuncs = ChromeUtils.import("chrome://openpgp/content/modules/funcs.jsm").EnigmailFuncs;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
+const EnigmailMime = ChromeUtils.import("chrome://openpgp/content/modules/mime.jsm").EnigmailMime;
+const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
+const EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs.jsm").EnigmailPrefs;
+const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
+const EnigmailDecryption = ChromeUtils.import("chrome://openpgp/content/modules/decryption.jsm").EnigmailDecryption;
+const EnigmailSingletons = ChromeUtils.import("chrome://openpgp/content/modules/singletons.jsm").EnigmailSingletons;
+const EnigmailHttpProxy = ChromeUtils.import("chrome://openpgp/content/modules/httpProxy.jsm").EnigmailHttpProxy;
+const EnigmailCryptoAPI = ChromeUtils.import("chrome://openpgp/content/modules/cryptoAPI.jsm").EnigmailCryptoAPI;
+
+const APPSHELL_MEDIATOR_CONTRACTID = "@mozilla.org/appshell/window-mediator;1";
+const PGPMIME_PROTO = "application/pgp-signature";
+
+const maxBufferLen = 102400;
+
+var gDebugLog = false;
+
+// MimeVerify Constructor
+function MimeVerify(protocol) {
+ if (!protocol) {
+ protocol = PGPMIME_PROTO;
+ }
+
+ this.protocol = protocol;
+ this.verifyEmbedded = false;
+ this.partiallySigned = false;
+ this.exitCode = null;
+ this.inStream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream);
+
+ if (EnigmailCompat.isMessageUriInPgpMime()) {
+ this.onDataAvailable = this.onDataAvailable68;
+ } else {
+ this.onDataAvailable = this.onDataAvailable60;
+ }
+}
+
+
+var EnigmailVerify = {
+ lastMsgWindow: null,
+ lastMsgUri: null,
+ manualMsgUri: null,
+
+ currentCtHandler: EnigmailConstants.MIME_HANDLER_UNDEF,
+
+ setMsgWindow: function(msgWindow, msgUriSpec) {
+ LOCAL_DEBUG("mimeVerify.jsm: setMsgWindow: " + msgUriSpec + "\n");
+
+ this.lastMsgWindow = msgWindow;
+ this.lastMsgUri = msgUriSpec;
+ },
+
+ newVerifier: function(protocol) {
+ EnigmailLog.DEBUG("mimeVerify.jsm: newVerifier: " + (protocol || "null") + "\n");
+
+ let v = new MimeVerify(protocol);
+ return v;
+ },
+
+ setManualUri: function(msgUriSpec) {
+ LOCAL_DEBUG("mimeVerify.jsm: setManualUri: " + msgUriSpec + "\n");
+ this.manualMsgUri = msgUriSpec;
+ },
+
+ getManualUri: function() {
+ EnigmailLog.DEBUG("mimeVerify.jsm: getManualUri\n");
+ return this.manualMsgUri;
+ },
+
+ /***
+ * register a PGP/MIME verify object the same way PGP/MIME encrypted mail is handled
+ */
+ registerContentTypeHandler: function() {
+ EnigmailLog.DEBUG("mimeVerify.jsm: registerContentTypeHandler\n");
+ let reg = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+
+ let pgpMimeClass = Components.classes["@mozilla.org/mimecth;1?type=multipart/encrypted"];
+
+ reg.registerFactory(
+ pgpMimeClass,
+ "Enigmail PGP/MIME verification",
+ "@mozilla.org/mimecth;1?type=multipart/signed",
+ null);
+ this.currentCtHandler = EnigmailConstants.MIME_HANDLER_PGPMIME;
+ },
+
+ unregisterContentTypeHandler: function() {
+ EnigmailLog.DEBUG("mimeVerify.jsm: unregisterContentTypeHandler\n");
+ let reg = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+
+ let sMimeClass = Components.classes["@mozilla.org/nsCMSDecoder;1"];
+ reg.registerFactory(sMimeClass, "S/MIME verification", "@mozilla.org/mimecth;1?type=multipart/signed", null);
+ this.currentCtHandler = EnigmailConstants.MIME_HANDLER_SMIME;
+ }
+
+};
+
+
+// MimeVerify implementation
+// verify the signature of PGP/MIME signed messages
+MimeVerify.prototype = {
+ dataCount: 0,
+ foundMsg: false,
+ startMsgStr: "",
+ msgWindow: null,
+ msgUriSpec: null,
+ statusDisplayed: false,
+ window: null,
+ inStream: null,
+ sigFile: null,
+ sigData: "",
+ mimePartNumber: "",
+
+ QueryInterface: EnigmailCompat.generateQI([Ci.nsIStreamListener]),
+
+ startStreaming: function(window, msgWindow, msgUriSpec) {
+ LOCAL_DEBUG("mimeVerify.jsm: startStreaming\n");
+
+ this.msgWindow = msgWindow;
+ this.msgUriSpec = msgUriSpec;
+ this.window = window;
+ var messenger = Cc["@mozilla.org/messenger;1"].getService(Ci.nsIMessenger);
+ var msgSvc = messenger.messageServiceFromURI(this.msgUriSpec);
+
+ msgSvc.streamMessage(this.msgUriSpec,
+ this,
+ this.msgWindow,
+ null,
+ false,
+ null,
+ false);
+ },
+
+ verifyData: function(window, msgWindow, msgUriSpec, data) {
+ LOCAL_DEBUG("mimeVerify.jsm: streamFromChannel\n");
+
+ this.msgWindow = msgWindow;
+ this.msgUriSpec = msgUriSpec;
+ this.window = window;
+ this.onStartRequest();
+ this.onTextData(data);
+ this.onStopRequest();
+ },
+
+ parseContentType: function() {
+ let contentTypeLine = this.mimeSvc.contentType;
+
+ // Eat up CRLF's.
+ contentTypeLine = contentTypeLine.replace(/[\r\n]/g, "");
+ EnigmailLog.DEBUG("mimeVerify.jsm: parseContentType: " + contentTypeLine + "\n");
+
+ let protoRx = RegExp("protocol\\s*=\\s*[\\'\\\"]" + this.protocol + "[\\\"\\']", "i");
+
+ if (contentTypeLine.search(/multipart\/signed/i) >= 0 &&
+ contentTypeLine.search(protoRx) > 0) {
+
+ EnigmailLog.DEBUG("mimeVerify.jsm: parseContentType: found MIME signed message\n");
+ this.foundMsg = true;
+ let hdr = EnigmailFuncs.getHeaderData(contentTypeLine);
+ hdr.boundary = hdr.boundary || "";
+ hdr.micalg = hdr.micalg || "";
+ this.boundary = hdr.boundary.replace(/['"]/g, "");
+ }
+ },
+
+ onStartRequest: function(request, uri) {
+ EnigmailLog.DEBUG("mimeVerify.jsm: onStartRequest\n"); // always log this one
+
+ this.mimeSvc = request.QueryInterface(Ci.nsIPgpMimeProxy);
+ this.msgUriSpec = EnigmailVerify.lastMsgUri;
+
+ if ("mimePart" in this.mimeSvc) {
+ this.mimePartNumber = this.mimeSvc.mimePart;
+ } else {
+ this.mimePartNumber = "";
+ }
+
+ if ("messageURI" in this.mimeSvc) {
+ this.uri = this.mimeSvc.messageURI;
+ } else {
+ if (uri) {
+ this.uri = uri.QueryInterface(Ci.nsIURI);
+ }
+ }
+
+ this.dataCount = 0;
+ this.foundMsg = false;
+ this.backgroundJob = false;
+ this.startMsgStr = "";
+ this.boundary = "";
+ this.proc = null;
+ this.closePipe = false;
+ this.pipe = null;
+ this.readMode = 0;
+ this.keepData = "";
+ this.last80Chars = "";
+ this.signedData = "";
+ this.statusStr = "";
+ this.returnStatus = null;
+ this.statusDisplayed = false;
+ this.protectedHeaders = null;
+ this.parseContentType();
+ },
+
+ /**
+ * onDataAvailable for TB <= 66
+ */
+ onDataAvailable60: function(req, ctxt, stream, offset, count) {
+ LOCAL_DEBUG("mimeVerify.jsm: onDataAvailable60: " + count + "\n");
+ if (count > 0) {
+ this.inStream.init(stream);
+ var data = this.inStream.read(count);
+ this.onTextData(data);
+ }
+ },
+
+ /**
+ * onDataAvailable for TB >= 67
+ */
+ onDataAvailable68: function(req, stream, offset, count) {
+ LOCAL_DEBUG("mimeVerify.jsm: onDataAvailable68: " + count + "\n");
+ if (count > 0) {
+ this.inStream.init(stream);
+ var data = this.inStream.read(count);
+ this.onTextData(data);
+ }
+ },
+
+ onTextData: function(data) {
+ LOCAL_DEBUG("mimeVerify.jsm: onTextData\n");
+
+ this.dataCount += data.length;
+
+ this.keepData += data;
+ if (this.readMode === 0) {
+ // header data
+ let i = this.findNextMimePart();
+ if (i >= 0) {
+ i += 2 + this.boundary.length;
+ if (this.keepData[i] == "\n") {
+ ++i;
+ } else if (this.keepData[i] == "\r") {
+ ++i;
+ if (this.keepData[i] == "\n") ++i;
+ }
+
+ this.keepData = this.keepData.substr(i);
+ data = this.keepData;
+ this.readMode = 1;
+ } else {
+ this.keepData = data.substr(-this.boundary.length - 3);
+ }
+ }
+
+ if (this.readMode === 1) {
+ // "real data"
+ if (data.indexOf("-") >= 0) { // only check current line for speed reasons
+ let i = this.findNextMimePart();
+ if (i >= 0) {
+ // end of "read data found"
+ if (this.keepData[i - 2] == '\r' && this.keepData[i - 1] == '\n') {
+ --i;
+ }
+
+ this.signedData = this.keepData.substr(0, i - 1);
+ this.keepData = this.keepData.substr(i);
+ this.readMode = 2;
+ }
+ } else
+ return;
+ }
+
+ if (this.readMode === 2) {
+ let i = this.keepData.indexOf("--" + this.boundary + "--");
+ if (i >= 0) {
+ // ensure that we keep everything until we got the "end" boundary
+ if (this.keepData[i - 2] == '\r' && this.keepData[i - 1] == '\n') {
+ --i;
+ }
+ this.keepData = this.keepData.substr(0, i - 1);
+ this.readMode = 3;
+ }
+ }
+
+ if (this.readMode === 3) {
+ // signature data
+ if (this.protocol === PGPMIME_PROTO) {
+ let xferEnc = this.getContentTransferEncoding();
+ if (xferEnc.search(/base64/i) >= 0) {
+ let bound = this.getBodyPart();
+ this.keepData = EnigmailData.decodeBase64(this.keepData.substring(bound.start, bound.end)) + "\n";
+ } else if (xferEnc.search(/quoted-printable/i) >= 0) {
+ let bound = this.getBodyPart();
+ let qp = this.keepData.substring(bound.start, bound.end);
+ this.keepData = EnigmailData.decodeQuotedPrintable(qp) + "\n";
+ }
+
+ // extract signature data
+ let s = Math.max(this.keepData.search(/^-----BEGIN PGP /m), 0);
+ let e = Math.max(this.keepData.search(/^-----END PGP /m), this.keepData.length - 30);
+ this.sigData = this.keepData.substring(s, e + 30);
+ } else {
+ this.sigData = "";
+ }
+
+ this.keepData = "";
+ this.readMode = 4; // ignore any further data
+ }
+
+ },
+
+ getBodyPart: function() {
+ let start = this.keepData.search(/(\n\n|\r\n\r\n)/);
+ if (start < 0) {
+ start = 0;
+ }
+ let end = this.keepData.indexOf("--" + this.boundary + "--") - 1;
+ if (end < 0) {
+ end = this.keepData.length;
+ }
+
+ return {
+ start: start,
+ end: end
+ };
+ },
+
+ // determine content-transfer encoding of mime part, assuming that whole
+ // message is in this.keepData
+ getContentTransferEncoding: function() {
+ let enc = "7bit";
+ let m = this.keepData.match(/^(content-transfer-encoding:)(.*)$/mi);
+ if (m && m.length > 2) {
+ enc = m[2].trim().toLowerCase();
+ }
+
+ return enc;
+ },
+
+
+ findNextMimePart: function() {
+ let startOk = false;
+ let endOk = false;
+
+ let i = this.keepData.indexOf("--" + this.boundary);
+ if (i === 0) startOk = true;
+ if (i > 0) {
+ if (this.keepData[i - 1] == '\r' || this.keepData[i - 1] == '\n') startOk = true;
+ }
+
+ if (!startOk) return -1;
+
+ if (i + this.boundary.length + 2 < this.keepData.length) {
+ if (this.keepData[i + this.boundary.length + 2] == '\r' ||
+ this.keepData[i + this.boundary.length + 2] == '\n' ||
+ this.keepData.substr(i + this.boundary.length + 2, 2) == '--') endOk = true;
+ }
+ // else
+ // endOk = true;
+
+ if (i >= 0 && startOk && endOk) {
+ return i;
+ }
+ return -1;
+ },
+
+ onStopRequest: function() {
+ EnigmailLog.DEBUG("mimeVerify.jsm: onStopRequest\n");
+
+ this.msgWindow = EnigmailVerify.lastMsgWindow;
+ this.msgUriSpec = EnigmailVerify.lastMsgUri;
+
+ let url = {};
+
+ this.backgroundJob = false;
+
+
+ // don't try to verify if no message found
+ // if (this.verifyEmbedded && (!this.foundMsg)) return; // TODO - check
+
+ if (this.readMode < 4) {
+ // we got incomplete data; simply return what we got
+ this.returnData(this.signedData.length > 0 ? this.signedData : this.keepData);
+
+ return;
+ }
+
+ this.protectedHeaders = EnigmailMime.extractProtectedHeaders(this.signedData);
+
+ if (this.protectedHeaders && this.protectedHeaders.startPos >= 0 && this.protectedHeaders.endPos > this.protectedHeaders.startPos) {
+ let r = this.signedData.substr(0, this.protectedHeaders.startPos) + this.signedData.substr(this.protectedHeaders.endPos);
+ this.returnData(r);
+ } else {
+ this.returnData(this.signedData);
+ }
+
+ // return if not verifying first mime part
+ if (this.mimePartNumber.length > 0 && this.mimePartNumber.search(/^1(\.1)?$/) < 0) return;
+
+ if (this.uri) {
+ // return if not decrypting currently displayed message (except if
+ // printing, replying, etc)
+
+ this.backgroundJob = (this.uri.spec.search(/[&?]header=(print|quotebody|enigmailConvert)/) >= 0);
+
+ try {
+ var messenger = Cc["@mozilla.org/messenger;1"].getService(Ci.nsIMessenger);
+
+ if (!EnigmailPrefs.getPref("autoDecrypt")) {
+ // "decrypt manually" mode
+ let manUrl = {};
+
+ if (EnigmailVerify.getManualUri()) {
+ manUrl = EnigmailCompat.getUrlFromUriSpec(EnigmailVerify.getManualUri());
+ } else {
+ manUrl = {
+ spec: "enigmail://invalid/message"
+ };
+ }
+
+ // print a message if not message explicitly decrypted
+ let currUrlSpec = this.uri.spec.replace(/(\?.*)(number=[0-9]*)(&.*)?$/, "?$2");
+ let manUrlSpec = manUrl.spec.replace(/(\?.*)(number=[0-9]*)(&.*)?$/, "?$2");
+
+
+ if ((!this.backgroundJob) && currUrlSpec != manUrlSpec) {
+ return; // this.handleManualDecrypt();
+ }
+ }
+
+ if (this.msgUriSpec) {
+ url = EnigmailCompat.getUrlFromUriSpec(this.msgUriSpec);
+ }
+
+ if (this.uri.spec.search(/[&?]header=[a-zA-Z0-9]*$/) < 0 &&
+ this.uri.spec.search(/[&?]part=[.0-9]+/) < 0 &&
+ this.uri.spec.search(/[&?]examineEncryptedParts=true/) < 0) {
+
+ if (this.uri.spec.search(/[&?]header=filter&.*$/) > 0)
+ return;
+
+ if (this.uri && url) {
+
+ if ("path" in url) {
+ // TB < 57
+ if (url.host !== this.uri.host ||
+ url.path !== this.uri.path)
+ return;
+ } else {
+ // TB >= 57
+ if (url.host !== this.uri.host ||
+ url.pathQueryRef !== this.uri.pathQueryRef)
+ return;
+ }
+ }
+ }
+ } catch (ex) {
+ EnigmailLog.writeException("mimeVerify.jsm", ex);
+ EnigmailLog.DEBUG("mimeVerify.jsm: error while processing " + this.msgUriSpec + "\n");
+ }
+ }
+
+ if (this.protocol === PGPMIME_PROTO) {
+ var windowManager = Cc[APPSHELL_MEDIATOR_CONTRACTID].getService(Ci.nsIWindowMediator);
+ var win = windowManager.getMostRecentWindow(null);
+
+ // create temp file holding signature data
+ this.sigFile = EnigmailFiles.getTempDirObj();
+ this.sigFile.append("data.sig");
+ this.sigFile.createUnique(this.sigFile.NORMAL_FILE_TYPE, 0x180);
+ EnigmailFiles.writeFileContents(this.sigFile, this.sigData, 0x180);
+
+ if (!EnigmailDecryption.isReady(win)) return;
+
+
+ let sigFileName = EnigmailFiles.getEscapedFilename(EnigmailFiles.getFilePath(this.sigFile));
+ let keyserver = EnigmailPrefs.getPref("autoKeyRetrieve");
+ let options = {
+ keyserver: keyserver,
+ keyserverProxy: EnigmailHttpProxy.getHttpProxy(keyserver),
+ fromAddr: EnigmailDecryption.getFromAddr(win),
+ mimeSignatureFile: sigFileName
+ };
+ const cApi = EnigmailCryptoAPI();
+
+ // ensure all lines end with CRLF as specified in RFC 3156, section 5
+ if (this.signedData.search(/[^\r]\n/) >= 0) {
+ this.signedData = this.signedData.replace(/\r\n/g, "\n").replace(/\n/g, "\r\n");
+ }
+
+ this.returnStatus = cApi.sync(cApi.verifyMime(this.signedData, options));
+ this.exitCode = this.returnStatus.exitCode;
+
+ if (this.partiallySigned)
+ this.returnStatus.statusFlags |= EnigmailConstants.PARTIALLY_PGP;
+
+ this.displayStatus();
+
+ if (this.sigFile) this.sigFile.remove(false);
+ }
+ },
+
+ // return data to libMime
+ returnData: function(data) {
+ EnigmailLog.DEBUG("mimeVerify.jsm: returnData: " + data.length + " bytes\n");
+
+ let m = data.match(/^(content-type: +)([\w/]+)/im);
+ if (m && m.length >= 3) {
+ let contentType = m[2];
+ if (contentType.search(/^text/i) === 0) {
+ // add multipart/mixed boundary to work around TB bug (empty forwarded message)
+ let bound = EnigmailMime.createBoundary();
+ data = 'Content-Type: multipart/mixed; boundary="' + bound + '"\n' +
+ 'Content-Disposition: inline\n\n--' +
+ bound + '\n' +
+ data +
+ '\n--' + bound + '--\n';
+ }
+ }
+
+ if ("outputDecryptedData" in this.mimeSvc) {
+ // TB >= 57
+ this.mimeSvc.outputDecryptedData(data, data.length);
+ } else {
+ let gConv = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
+ gConv.setData(data, data.length);
+ try {
+ this.mimeSvc.onStartRequest(null, null);
+ this.mimeSvc.onDataAvailable(null, null, gConv, 0, data.length);
+ this.mimeSvc.onStopRequest(null, null, 0);
+ } catch (ex) {
+ EnigmailLog.ERROR("mimeVerify.jsm: returnData(): mimeSvc.onDataAvailable failed:\n" + ex.toString());
+ }
+ }
+ },
+
+ setMsgWindow: function(msgWindow, msgUriSpec) {
+ EnigmailLog.DEBUG("mimeVerify.jsm: setMsgWindow: " + msgUriSpec + "\n");
+
+ if (!this.msgWindow) {
+ this.msgWindow = msgWindow;
+ this.msgUriSpec = msgUriSpec;
+ }
+ },
+
+ displayStatus: function() {
+ EnigmailLog.DEBUG("mimeVerify.jsm: displayStatus\n");
+ if (this.exitCode === null || this.msgWindow === null || this.statusDisplayed || this.backgroundJob)
+ return;
+
+ try {
+ LOCAL_DEBUG("mimeVerify.jsm: displayStatus displaying result\n");
+ let headerSink = EnigmailSingletons.messageReader;
+
+ if (this.protectedHeaders) {
+ headerSink.processDecryptionResult(this.uri, "modifyMessageHeaders", JSON.stringify(this.protectedHeaders.newHeaders), this.mimePartNumber);
+ }
+
+ if (headerSink) {
+ headerSink.updateSecurityStatus(this.lastMsgUri,
+ this.exitCode,
+ this.returnStatus.statusFlags,
+ this.returnStatus.keyId,
+ this.returnStatus.userId,
+ this.returnStatus.sigDetails,
+ this.returnStatus.errorMsg,
+ this.returnStatus.blockSeparation,
+ this.uri,
+ JSON.stringify({
+ encryptedTo: this.returnStatus.encToDetails
+ }),
+ this.mimePartNumber);
+ }
+ this.statusDisplayed = true;
+ } catch (ex) {
+ EnigmailLog.writeException("mimeVerify.jsm", ex);
+ }
+ }
+};
+
+
+////////////////////////////////////////////////////////////////////
+// General-purpose functions, not exported
+
+function LOCAL_DEBUG(str) {
+ if (gDebugLog) EnigmailLog.DEBUG(str);
+}
+
+function initModule() {
+ var env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
+ var nspr_log_modules = env.get("NSPR_LOG_MODULES");
+ var matches = nspr_log_modules.match(/mimeVerify:(\d+)/);
+
+ if (matches && (matches.length > 1)) {
+ if (matches[1] > 2) gDebugLog = true;
+ }
+}
+
+initModule();
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/msgRead.jsm
@@ -0,0 +1,275 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailMsgRead"];
+
+/**
+ * Message-reading related functions
+ */
+
+const EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs.jsm").EnigmailPrefs;
+const EnigmailApp = ChromeUtils.import("chrome://openpgp/content/modules/app.jsm").EnigmailApp;
+const EnigmailVersioning = ChromeUtils.import("chrome://openpgp/content/modules/versioning.jsm").EnigmailVersioning;
+const EnigmailKeyRing = ChromeUtils.import("chrome://openpgp/content/modules/keyRing.jsm").EnigmailKeyRing;
+const EnigmailFuncs = ChromeUtils.import("chrome://openpgp/content/modules/funcs.jsm").EnigmailFuncs;
+const EnigmailAutocrypt = ChromeUtils.import("chrome://openpgp/content/modules/autocrypt.jsm").EnigmailAutocrypt;
+const EnigmailCompat = ChromeUtils.import("chrome://openpgp/content/modules/compat.jsm").EnigmailCompat;
+
+const ExtraHeaders = ["autocrypt", "openpgp"];
+
+var EnigmailMsgRead = {
+ /**
+ * Ensure that Thunderbird prepares certain headers during message reading
+ */
+ ensureExtraAddonHeaders: function() {
+ let r = EnigmailPrefs.getPrefRoot();
+
+ // is the Mozilla Platform number >= 59?
+ const PREF_NAME = "mailnews.headers.extraAddonHeaders";
+
+ try {
+ let hdr = r.getCharPref(PREF_NAME);
+
+ if (hdr !== "*") { // do nothing if extraAddonHeaders is "*" (all headers)
+ for (let h of ExtraHeaders) {
+ let sr = new RegExp("\\b" + h + "\\b", "i");
+ if (hdr.search(h) < 0) {
+ if (hdr.length > 0) hdr += " ";
+ hdr += h;
+ }
+ }
+
+ r.setCharPref(PREF_NAME, hdr);
+ }
+ }
+ catch (x) {}
+ },
+
+ /**
+ * Get a mail URL from a uriSpec
+ *
+ * @param uriSpec: String - URI of the desired message
+ *
+ * @return Object: nsIURL or nsIMsgMailNewsUrl object
+ */
+ getUrlFromUriSpec: function(uriSpec) {
+ return EnigmailCompat.getUrlFromUriSpec(uriSpec);
+ },
+
+ /**
+ * Determine if an attachment is possibly signed
+ */
+ checkSignedAttachment: function(attachmentObj, index, currentAttachments) {
+ function escapeRegex(string) {
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
+ }
+
+ var attachmentList;
+ if (index !== null) {
+ attachmentList = attachmentObj;
+ }
+ else {
+ attachmentList = currentAttachments;
+ for (let i = 0; i < attachmentList.length; i++) {
+ if (attachmentList[i].url == attachmentObj.url) {
+ index = i;
+ break;
+ }
+ }
+ if (index === null) return false;
+ }
+
+ var signed = false;
+ var findFile;
+
+ var attName = this.getAttachmentName(attachmentList[index]).toLowerCase().replace(/\+/g, "\\+");
+
+ // check if filename is a signature
+ if ((this.getAttachmentName(attachmentList[index]).search(/\.(sig|asc)$/i) > 0) ||
+ (attachmentList[index].contentType.match(/^application\/pgp-signature/i))) {
+ findFile = new RegExp(escapeRegex(attName.replace(/\.(sig|asc)$/, "")));
+ }
+ else if (attName.search(/\.pgp$/i) > 0) {
+ findFile = new RegExp(escapeRegex(attName.replace(/\.pgp$/, "")) + "(\\.pgp)?\\.(sig|asc)$");
+ }
+ else {
+ findFile = new RegExp(escapeRegex(attName) + "\\.(sig|asc)$");
+ }
+
+ for (let i in attachmentList) {
+ if ((i != index) &&
+ (this.getAttachmentName(attachmentList[i]).toLowerCase().search(findFile) === 0))
+ signed = true;
+ }
+
+ return signed;
+ },
+
+ /**
+ * Get the name of an attachment from the attachment object
+ */
+ getAttachmentName: function(attachment) {
+ return attachment.name;
+ },
+
+
+ /**
+ * Escape text such that it can be used as HTML text
+ */
+ escapeTextForHTML: function(text, hyperlink) {
+ // Escape special characters
+ if (text.indexOf("&") > -1)
+ text = text.replace(/&/g, "&");
+
+ if (text.indexOf("<") > -1)
+ text = text.replace(/</g, "<");
+
+ if (text.indexOf(">") > -1)
+ text = text.replace(/>/g, ">");
+
+ if (text.indexOf("\"") > -1)
+ text = text.replace(/"/g, """);
+
+ if (!hyperlink)
+ return text;
+
+ // Hyperlink email addresses (we accept at most 1024 characters before and after the @)
+ var addrs = text.match(/\b[A-Za-z0-9_+.-]{1,1024}@[A-Za-z0-9.-]{1,1024}\b/g);
+
+ var newText, offset, loc;
+ if (addrs && addrs.length) {
+ newText = "";
+ offset = 0;
+
+ for (var j = 0; j < addrs.length; j++) {
+ var addr = addrs[j];
+
+ loc = text.indexOf(addr, offset);
+ if (loc < offset)
+ break;
+
+ if (loc > offset)
+ newText += text.substr(offset, loc - offset);
+
+ // Strip any period off the end of address
+ addr = addr.replace(/[.]$/, "");
+
+ if (!addr.length)
+ continue;
+
+ newText += "<a href=\"mailto:" + addr + "\">" + addr + "</a>";
+
+ offset = loc + addr.length;
+ }
+
+ newText += text.substr(offset, text.length - offset);
+
+ text = newText;
+ }
+
+ // Hyperlink URLs (we don't accept URLS or more than 1024 characters length)
+ var urls = text.match(/\b(http|https|ftp):\S{1,1024}\s/g);
+
+ if (urls && urls.length) {
+ newText = "";
+ offset = 0;
+
+ for (var k = 0; k < urls.length; k++) {
+ var url = urls[k];
+
+ loc = text.indexOf(url, offset);
+ if (loc < offset)
+ break;
+
+ if (loc > offset)
+ newText += text.substr(offset, loc - offset);
+
+ // Strip delimiters off the end of URL
+ url = url.replace(/\s$/, "");
+ url = url.replace(/([),.']|>|")$/, "");
+
+ if (!url.length)
+ continue;
+
+ newText += "<a href=\"" + url + "\">" + url + "</a>";
+
+ offset = loc + url.length;
+ }
+
+ newText += text.substr(offset, text.length - offset);
+
+ text = newText;
+ }
+
+ return text;
+ },
+
+ /**
+ * Match the key to the sender's from address
+ *
+ * @param {String} keyId: signing key ID
+ * @param {String} fromAddr: sender's email address
+ *
+ * @return Promise<String>: matching email address
+ */
+ matchUidToSender: function(keyId, fromAddr) {
+ if ((!fromAddr) || !keyId) {
+ return null;
+ }
+
+ try {
+ fromAddr = EnigmailFuncs.stripEmail(fromAddr).toLowerCase();
+ }
+ catch (ex) {}
+
+ let keyObj = EnigmailKeyRing.getKeyById(keyId);
+ if (!keyObj) return null;
+
+ let userIdList = keyObj.userIds;
+
+ try {
+ for (let i = 0; i < userIdList.length; i++) {
+ if (fromAddr == EnigmailFuncs.stripEmail(userIdList[i].userId).toLowerCase()) {
+ return EnigmailFuncs.stripEmail(userIdList[i].userId);
+ }
+ }
+
+ // // uid not found, try Autocrypt keystore
+ // let acList = await EnigmailAutocrypt.getOpenPGPKeyForEmail([fromAddr]);
+ // for (let i = 0; i < acList.length; i++) {
+ // if (acList[i].fpr == keyObj.fpr) {
+ // return fromAddr;
+ // }
+ // }
+ }
+ catch (ex) {}
+ return null;
+ },
+
+ searchQuotedPgp: function(node) {
+ if (node.nodeName.toLowerCase() === "blockquote" &&
+ node.textContent.indexOf("-----BEGIN PGP ") >= 0) {
+ return true;
+ }
+
+ if (node.firstChild && this.searchQuotedPgp(node.firstChild)) {
+ return true;
+ }
+
+ if (node.nextSibling && this.searchQuotedPgp(node.nextSibling)) {
+ return true;
+ }
+
+ return false;
+ },
+
+ trimAllLines: function(txt) {
+ return txt.replace(/^[ \t]+/mg, "");
+ }
+
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/openpgp.jsm
@@ -0,0 +1,160 @@
+/*
+ * This Source Code Form is licensed under the GNU LGPL 3.0 license.
+ *
+ */
+
+"use strict";
+
+/**
+ * This code is taken from openpgp.js
+ *
+ * Do OpenPGP packet parsing
+ */
+
+/* eslint no-invalid-this: 0 */
+
+var EXPORTED_SYMBOLS = ["EnigmailOpenPGP"];
+
+var crc_table = [0x00000000, 0x00864cfb, 0x018ad50d, 0x010c99f6, 0x0393e6e1, 0x0315aa1a, 0x021933ec, 0x029f7f17, 0x07a18139, 0x0727cdc2, 0x062b5434, 0x06ad18cf, 0x043267d8, 0x04b42b23,
+ 0x05b8b2d5, 0x053efe2e, 0x0fc54e89, 0x0f430272, 0x0e4f9b84, 0x0ec9d77f, 0x0c56a868, 0x0cd0e493, 0x0ddc7d65, 0x0d5a319e, 0x0864cfb0, 0x08e2834b, 0x09ee1abd, 0x09685646, 0x0bf72951,
+ 0x0b7165aa, 0x0a7dfc5c, 0x0afbb0a7, 0x1f0cd1e9, 0x1f8a9d12, 0x1e8604e4, 0x1e00481f, 0x1c9f3708, 0x1c197bf3, 0x1d15e205, 0x1d93aefe, 0x18ad50d0, 0x182b1c2b, 0x192785dd, 0x19a1c926,
+ 0x1b3eb631, 0x1bb8faca, 0x1ab4633c, 0x1a322fc7, 0x10c99f60, 0x104fd39b, 0x11434a6d, 0x11c50696, 0x135a7981, 0x13dc357a, 0x12d0ac8c, 0x1256e077, 0x17681e59, 0x17ee52a2, 0x16e2cb54,
+ 0x166487af, 0x14fbf8b8, 0x147db443, 0x15712db5, 0x15f7614e, 0x3e19a3d2, 0x3e9fef29, 0x3f9376df, 0x3f153a24, 0x3d8a4533, 0x3d0c09c8, 0x3c00903e, 0x3c86dcc5, 0x39b822eb, 0x393e6e10,
+ 0x3832f7e6, 0x38b4bb1d, 0x3a2bc40a, 0x3aad88f1, 0x3ba11107, 0x3b275dfc, 0x31dced5b, 0x315aa1a0, 0x30563856, 0x30d074ad, 0x324f0bba, 0x32c94741, 0x33c5deb7, 0x3343924c, 0x367d6c62,
+ 0x36fb2099, 0x37f7b96f, 0x3771f594, 0x35ee8a83, 0x3568c678, 0x34645f8e, 0x34e21375, 0x2115723b, 0x21933ec0, 0x209fa736, 0x2019ebcd, 0x228694da, 0x2200d821, 0x230c41d7, 0x238a0d2c,
+ 0x26b4f302, 0x2632bff9, 0x273e260f, 0x27b86af4, 0x252715e3, 0x25a15918, 0x24adc0ee, 0x242b8c15, 0x2ed03cb2, 0x2e567049, 0x2f5ae9bf, 0x2fdca544, 0x2d43da53, 0x2dc596a8, 0x2cc90f5e,
+ 0x2c4f43a5, 0x2971bd8b, 0x29f7f170, 0x28fb6886, 0x287d247d, 0x2ae25b6a, 0x2a641791, 0x2b688e67, 0x2beec29c, 0x7c3347a4, 0x7cb50b5f, 0x7db992a9, 0x7d3fde52, 0x7fa0a145, 0x7f26edbe,
+ 0x7e2a7448, 0x7eac38b3, 0x7b92c69d, 0x7b148a66, 0x7a181390, 0x7a9e5f6b, 0x7801207c, 0x78876c87, 0x798bf571, 0x790db98a, 0x73f6092d, 0x737045d6, 0x727cdc20, 0x72fa90db, 0x7065efcc,
+ 0x70e3a337, 0x71ef3ac1, 0x7169763a, 0x74578814, 0x74d1c4ef, 0x75dd5d19, 0x755b11e2, 0x77c46ef5, 0x7742220e, 0x764ebbf8, 0x76c8f703, 0x633f964d, 0x63b9dab6, 0x62b54340, 0x62330fbb,
+ 0x60ac70ac, 0x602a3c57, 0x6126a5a1, 0x61a0e95a, 0x649e1774, 0x64185b8f, 0x6514c279, 0x65928e82, 0x670df195, 0x678bbd6e, 0x66872498, 0x66016863, 0x6cfad8c4, 0x6c7c943f, 0x6d700dc9,
+ 0x6df64132, 0x6f693e25, 0x6fef72de, 0x6ee3eb28, 0x6e65a7d3, 0x6b5b59fd, 0x6bdd1506, 0x6ad18cf0, 0x6a57c00b, 0x68c8bf1c, 0x684ef3e7, 0x69426a11, 0x69c426ea, 0x422ae476, 0x42aca88d,
+ 0x43a0317b, 0x43267d80, 0x41b90297, 0x413f4e6c, 0x4033d79a, 0x40b59b61, 0x458b654f, 0x450d29b4, 0x4401b042, 0x4487fcb9, 0x461883ae, 0x469ecf55, 0x479256a3, 0x47141a58, 0x4defaaff,
+ 0x4d69e604, 0x4c657ff2, 0x4ce33309, 0x4e7c4c1e, 0x4efa00e5, 0x4ff69913, 0x4f70d5e8, 0x4a4e2bc6, 0x4ac8673d, 0x4bc4fecb, 0x4b42b230, 0x49ddcd27, 0x495b81dc, 0x4857182a, 0x48d154d1,
+ 0x5d26359f, 0x5da07964, 0x5cace092, 0x5c2aac69, 0x5eb5d37e, 0x5e339f85, 0x5f3f0673, 0x5fb94a88, 0x5a87b4a6, 0x5a01f85d, 0x5b0d61ab, 0x5b8b2d50, 0x59145247, 0x59921ebc, 0x589e874a,
+ 0x5818cbb1, 0x52e37b16, 0x526537ed, 0x5369ae1b, 0x53efe2e0, 0x51709df7, 0x51f6d10c, 0x50fa48fa, 0x507c0401, 0x5542fa2f, 0x55c4b6d4, 0x54c82f22, 0x544e63d9, 0x56d11cce, 0x56575035,
+ 0x575bc9c3, 0x57dd8538
+];
+
+function initialize() {
+ const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+
+ EnigmailLog.DEBUG("openpgp.jsm: initialize()\n");
+
+}
+
+var EnigmailOpenPGP = {
+ get openpgp() {
+ if (!gOpenPGPLib) {
+ initialize();
+ }
+
+ return gOpenPGPLib;
+ },
+
+ armor: {
+ multipart_section: 0,
+ multipart_last: 1,
+ signed: 2,
+ message: 3,
+ public_key: 4,
+ private_key: 5,
+ signature: 6
+ },
+
+
+ enigmailFuncs: {
+
+ /**
+ * Convert a string to an Uint8Array
+ *
+ * @param str: String with binary data
+ * @return Uint8Array
+ */
+ str2Uint8Array: function(str) {
+ var buf = new ArrayBuffer(str.length);
+ var bufView = new Uint8Array(buf);
+ for (var i = 0, strLen = str.length; i < strLen; i++) {
+ bufView[i] = str.charCodeAt(i);
+ }
+ return bufView;
+ },
+
+ /**
+ * Create CRC24 checksum
+ *
+ * @param input: Uint8Array of input data
+ *
+ * @return Number
+ */
+ createcrc24: function(input) {
+ var crc = 0xB704CE;
+ var index = 0;
+
+ while (input.length - index > 16) {
+ crc = crc << 8 ^ crc_table[(crc >> 16 ^ input[index]) & 0xff];
+ crc = crc << 8 ^ crc_table[(crc >> 16 ^ input[index + 1]) & 0xff];
+ crc = crc << 8 ^ crc_table[(crc >> 16 ^ input[index + 2]) & 0xff];
+ crc = crc << 8 ^ crc_table[(crc >> 16 ^ input[index + 3]) & 0xff];
+ crc = crc << 8 ^ crc_table[(crc >> 16 ^ input[index + 4]) & 0xff];
+ crc = crc << 8 ^ crc_table[(crc >> 16 ^ input[index + 5]) & 0xff];
+ crc = crc << 8 ^ crc_table[(crc >> 16 ^ input[index + 6]) & 0xff];
+ crc = crc << 8 ^ crc_table[(crc >> 16 ^ input[index + 7]) & 0xff];
+ crc = crc << 8 ^ crc_table[(crc >> 16 ^ input[index + 8]) & 0xff];
+ crc = crc << 8 ^ crc_table[(crc >> 16 ^ input[index + 9]) & 0xff];
+ crc = crc << 8 ^ crc_table[(crc >> 16 ^ input[index + 10]) & 0xff];
+ crc = crc << 8 ^ crc_table[(crc >> 16 ^ input[index + 11]) & 0xff];
+ crc = crc << 8 ^ crc_table[(crc >> 16 ^ input[index + 12]) & 0xff];
+ crc = crc << 8 ^ crc_table[(crc >> 16 ^ input[index + 13]) & 0xff];
+ crc = crc << 8 ^ crc_table[(crc >> 16 ^ input[index + 14]) & 0xff];
+ crc = crc << 8 ^ crc_table[(crc >> 16 ^ input[index + 15]) & 0xff];
+ index += 16;
+ }
+
+ for (var j = index; j < input.length; j++) {
+ crc = crc << 8 ^ crc_table[(crc >> 16 ^ input[index++]) & 0xff];
+ }
+ return crc & 0xffffff;
+ },
+
+ /**
+ * Create an ASCII armored string from binary data. The message data is NOT
+ * checked for correctness, only the CRC is added at the end.
+ *
+ * @param msgType: Number - type of OpenPGP message to create (ARMOR Enum)
+ * @param str: String - binary OpenPGP message
+ *
+ * @return String: ASCII armored OpenPGP message
+ */
+ bytesToArmor: function(msgType, str) {
+
+ const ARMOR_TYPE = EnigmailOpenPGP.armor;
+
+ let hdr = "";
+ switch (msgType) {
+ case ARMOR_TYPE.signed:
+ case ARMOR_TYPE.message:
+ hdr = "MESSAGE";
+ break;
+ case ARMOR_TYPE.public_key:
+ hdr = "PUBLIC KEY BLOCK";
+ break;
+ case ARMOR_TYPE.private_key:
+ hdr = "PRIVATE KEY BLOCK";
+ break;
+ case ARMOR_TYPE.signature:
+ hdr = "SIGNATURE";
+ break;
+ }
+
+ let crc = EnigmailOpenPGP.enigmailFuncs.createcrc24(EnigmailOpenPGP.enigmailFuncs.str2Uint8Array(str));
+ let crcAsc = String.fromCharCode(crc >> 16) + String.fromCharCode(crc >> 8 & 0xFF) + String.fromCharCode(crc & 0xFF);
+
+ let s = "-----BEGIN PGP " + hdr + "-----\n\n" +
+ btoa(str).replace(/(.{72})/g, "$1\n") + "\n" +
+ "=" + btoa(crcAsc) + "\n" +
+ "-----END PGP " + hdr + "-----\n";
+
+ return s;
+ }
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/os.jsm
@@ -0,0 +1,96 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+const EXPORTED_SYMBOLS = ["EnigmailOS"];
+
+const XPCOM_APPINFO = "@mozilla.org/xre/app-info;1";
+
+// const getExecution = EnigmailLazy.loader("enigmail/execution.jsm", "EnigmailExecution");
+
+let operatingSystem = null;
+
+function getOS() {
+ if (operatingSystem === null) {
+ operatingSystem = Cc[XPCOM_APPINFO].getService(Ci.nsIXULRuntime).OS;
+ }
+ return operatingSystem;
+}
+
+function isDosLike() {
+ return getOS() === "WINNT" || getOS() === "OS2";
+}
+
+function isMac() {
+ return getOS() === "Darwin";
+}
+
+function isWin32() {
+ return getOS() === "WINNT";
+}
+
+var EnigmailOS = {
+ /*
+ * getOS uses the Mozilla nsIXULRuntime Component to retrieve the OS Target
+ *
+ * @return String - OS Identifier
+ */
+ getOS: getOS,
+
+ /**
+ * isDosLike identifies whether the host computer is MS-DOS based
+ *
+ * @return Boolean - True if local host is MS-DOS based. False otherwise.
+ */
+ isDosLike: isDosLike(),
+
+ /**
+ * isWin32 identifies whether the running system is a Windows (32 or 64 bit) machine
+ *
+ * @return Boolean - True if local host is a Windows machine. False otherwise.
+ */
+ isWin32: isWin32(),
+
+ /**
+ * isMac identifies whether the running system is a Mac
+ *
+ * @return Boolean - True if local host is a derivative of Darwin. False otherwise.
+ */
+ isMac: isMac(),
+
+ /**
+ * get a Windows registry value (string)
+ *
+ * @param keyPath String - the path of the registry (e.g. Software\\GNU\\GnuPG)
+ * @param keyName String - the name of the key to get (e.g. InstallDir)
+ * @param rootKey Number - HKLM, HKCU, etc. (according to constants in nsIWindowsRegKey)
+ *
+ * @return String - the found registry value (or empty string if not found)
+ */
+ getWinRegistryString: function(keyPath, keyName, rootKey) {
+ const registry = Cc["@mozilla.org/windows-registry-key;1"].createInstance(Ci.nsIWindowsRegKey);
+
+ let retval = "";
+ try {
+ registry.open(rootKey, keyPath, registry.ACCESS_READ);
+ retval = registry.readStringValue(keyName);
+ registry.close();
+ }
+ catch (ex) {}
+
+ return retval;
+ },
+
+ getNullFile: function() {
+ if (this.isDosLike) {
+ return "NUL";
+ }
+ else {
+ return "/dev/null";
+ }
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/passwordCheck.jsm
@@ -0,0 +1,200 @@
+/*jshint -W097 */
+/*
+ Taken from jquery.complexify (WTFPL 2.0)
+*/
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailPasswordCheck"];
+
+var EnigmailPasswordCheck = {
+ /**
+ * Check password quality
+ *
+ * password: String: the password to evaluate
+ *
+ * return: object: valid: Boolean - is password valid or not
+ * complexity: Number - complexity of password (values between 0 and 100)
+ */
+ checkQuality: function(password) {
+ return evaluateSecurity(password);
+ }
+};
+
+/*********
+ Helper functions and variables
+*/
+
+
+const COMPLEXIFY_BANLIST =
+ '123456|password|12345678|1234|pussy|12345|dragon|qwerty|696969|mustang|letmein|baseball|master|michael|football|shadow|monkey|abc123|pass|fuckme|6969|jordan|harley|ranger|iwantu|jennifer|hunter|fuck|2000|test|batman|trustno1|thomas|tigger|robert|access|love|buster|1234567|soccer|hockey|killer|george|sexy|andrew|charlie|superman|asshole|fuckyou|dallas|jessica|panties|pepper|1111|austin|william|daniel|golfer|summer|heather|hammer|yankees|joshua|maggie|biteme|enter|ashley|thunder|cowboy|silver|richard|fucker|orange|merlin|michelle|corvette|bigdog|cheese|matthew|121212|patrick|martin|freedom|ginger|blowjob|nicole|sparky|yellow|camaro|secret|dick|falcon|taylor|111111|131313|123123|bitch|hello|scooter|please|porsche|guitar|chelsea|black|diamond|nascar|jackson|cameron|654321|computer|amanda|wizard|xxxxxxxx|money|phoenix|mickey|bailey|knight|iceman|tigers|purple|andrea|horny|dakota|aaaaaa|player|sunshine|morgan|starwars|boomer|cowboys|edward|charles|girls|booboo|coffee|xxxxxx|bulldog|ncc1701|rabbit|peanut|john|johnny|gandalf|spanky|winter|brandy|compaq|carlos|tennis|james|mike|brandon|fender|anthony|blowme|ferrari|cookie|chicken|maverick|chicago|joseph|diablo|sexsex|hardcore|666666|willie|welcome|chris|panther|yamaha|justin|banana|driver|marine|angels|fishing|david|maddog|hooters|wilson|butthead|dennis|fucking|captain|bigdick|chester|smokey|xavier|steven|viking|snoopy|blue|eagles|winner|samantha|house|miller|flower|jack|firebird|butter|united|turtle|steelers|tiffany|zxcvbn|tomcat|golf|bond007|bear|tiger|doctor|gateway|gators|angel|junior|thx1138|porno|badboy|debbie|spider|melissa|booger|1212|flyers|fish|porn|matrix|teens|scooby|jason|walter|cumshot|boston|braves|yankee|lover|barney|victor|tucker|princess|mercedes|5150|doggie|' +
+ 'zzzzzz|gunner|horney|bubba|2112|fred|johnson|xxxxx|tits|member|boobs|donald|bigdaddy|bronco|penis|voyager|rangers|birdie|trouble|white|topgun|bigtits|bitches|green|super|qazwsx|magic|lakers|rachel|slayer|scott|2222|asdf|video|london|7777|marlboro|srinivas|internet|action|carter|jasper|monster|teresa|jeremy|11111111|bill|crystal|peter|pussies|cock|beer|rocket|theman|oliver|prince|beach|amateur|7777777|muffin|redsox|star|testing|shannon|murphy|frank|hannah|dave|eagle1|11111|mother|nathan|raiders|steve|forever|angela|viper|ou812|jake|lovers|suckit|gregory|buddy|whatever|young|nicholas|lucky|helpme|jackie|monica|midnight|college|baby|cunt|brian|mark|startrek|sierra|leather|232323|4444|beavis|bigcock|happy|sophie|ladies|naughty|giants|booty|blonde|fucked|golden|0|fire|sandra|pookie|packers|einstein|dolphins|chevy|winston|warrior|sammy|slut|8675309|zxcvbnm|nipples|power|victoria|asdfgh|vagina|toyota|travis|hotdog|paris|rock|xxxx|extreme|redskins|erotic|dirty|ford|freddy|arsenal|access14|wolf|nipple|iloveyou|alex|florida|eric|legend|movie|success|rosebud|jaguar|great|cool|cooper|1313|scorpio|mountain|madison|987654|brazil|lauren|japan|naked|squirt|stars|apple|alexis|aaaa|bonnie|peaches|jasmine|kevin|matt|qwertyui|danielle|beaver|4321|4128|runner|swimming|dolphin|gordon|casper|stupid|shit|saturn|gemini|apples|august|3333|canada|blazer|cumming|hunting|kitty|rainbow|112233|arthur|cream|calvin|shaved|surfer|samson|kelly|paul|mine|king|racing|5555|eagle|hentai|newyork|little|redwings|smith|sticky|cocacola|animal|broncos|private|skippy|marvin|blondes|enjoy|girl|apollo|parker|qwert|time|sydney|women|voodoo|magnum|juice|abgrtyu|777777|dreams|maxwell|music|rush2112|russia|scorpion|rebecca|tester|mistress|phantom|billy|6666|albert|111111|11111111|112233|' +
+ '121212|123123|123456|1234567|12345678|131313|232323|654321|666666|696969|777777|7777777|8675309|987654|abcdef|password1|password12|password123|twitter'.split('|');
+
+const options = {
+ minimumChars: 8,
+ strengthScaleFactor: 1,
+ bannedPasswords: COMPLEXIFY_BANLIST,
+ banMode: 'strict' // (strict|loose)
+};
+
+const MIN_COMPLEXITY = 30; // 8 chars with Upper, Lower and Number
+//var MAX_COMPLEXITY = 120; // 25 chars, all charsets
+const MAX_COMPLEXITY = 60;
+
+const CHARSETS = [
+ // Commonly Used
+ ////////////////////
+ [0x0020, 0x0020], // Space
+ [0x0030, 0x0039], // Numbers
+ [0x0041, 0x005A], // Uppercase
+ [0x0061, 0x007A], // Lowercase
+ [0x0021, 0x002F], // Punctuation
+ [0x003A, 0x0040], // Punctuation
+ [0x005B, 0x0060], // Punctuation
+ [0x007B, 0x007E], // Punctuation
+ // Everything Else
+ ////////////////////
+ [0x0080, 0x00FF], // Latin-1 Supplement
+ [0x0100, 0x017F], // Latin Extended-A
+ [0x0180, 0x024F], // Latin Extended-B
+ [0x0250, 0x02AF], // IPA Extensions
+ [0x02B0, 0x02FF], // Spacing Modifier Letters
+ [0x0300, 0x036F], // Combining Diacritical Marks
+ [0x0370, 0x03FF], // Greek
+ [0x0400, 0x04FF], // Cyrillic
+ [0x0530, 0x058F], // Armenian
+ [0x0590, 0x05FF], // Hebrew
+ [0x0600, 0x06FF], // Arabic
+ [0x0700, 0x074F], // Syriac
+ [0x0780, 0x07BF], // Thaana
+ [0x0900, 0x097F], // Devanagari
+ [0x0980, 0x09FF], // Bengali
+ [0x0A00, 0x0A7F], // Gurmukhi
+ [0x0A80, 0x0AFF], // Gujarati
+ [0x0B00, 0x0B7F], // Oriya
+ [0x0B80, 0x0BFF], // Tamil
+ [0x0C00, 0x0C7F], // Telugu
+ [0x0C80, 0x0CFF], // Kannada
+ [0x0D00, 0x0D7F], // Malayalam
+ [0x0D80, 0x0DFF], // Sinhala
+ [0x0E00, 0x0E7F], // Thai
+ [0x0E80, 0x0EFF], // Lao
+ [0x0F00, 0x0FFF], // Tibetan
+ [0x1000, 0x109F], // Myanmar
+ [0x10A0, 0x10FF], // Georgian
+ [0x1100, 0x11FF], // Hangul Jamo
+ [0x1200, 0x137F], // Ethiopic
+ [0x13A0, 0x13FF], // Cherokee
+ [0x1400, 0x167F], // Unified Canadian Aboriginal Syllabics
+ [0x1680, 0x169F], // Ogham
+ [0x16A0, 0x16FF], // Runic
+ [0x1780, 0x17FF], // Khmer
+ [0x1800, 0x18AF], // Mongolian
+ [0x1E00, 0x1EFF], // Latin Extended Additional
+ [0x1F00, 0x1FFF], // Greek Extended
+ [0x2000, 0x206F], // General Punctuation
+ [0x2070, 0x209F], // Superscripts and Subscripts
+ [0x20A0, 0x20CF], // Currency Symbols
+ [0x20D0, 0x20FF], // Combining Marks for Symbols
+ [0x2100, 0x214F], // Letterlike Symbols
+ [0x2150, 0x218F], // Number Forms
+ [0x2190, 0x21FF], // Arrows
+ [0x2200, 0x22FF], // Mathematical Operators
+ [0x2300, 0x23FF], // Miscellaneous Technical
+ [0x2400, 0x243F], // Control Pictures
+ [0x2440, 0x245F], // Optical Character Recognition
+ [0x2460, 0x24FF], // Enclosed Alphanumerics
+ [0x2500, 0x257F], // Box Drawing
+ [0x2580, 0x259F], // Block Elements
+ [0x25A0, 0x25FF], // Geometric Shapes
+ [0x2600, 0x26FF], // Miscellaneous Symbols
+ [0x2700, 0x27BF], // Dingbats
+ [0x2800, 0x28FF], // Braille Patterns
+ [0x2E80, 0x2EFF], // CJK Radicals Supplement
+ [0x2F00, 0x2FDF], // Kangxi Radicals
+ [0x2FF0, 0x2FFF], // Ideographic Description Characters
+ [0x3000, 0x303F], // CJK Symbols and Punctuation
+ [0x3040, 0x309F], // Hiragana
+ [0x30A0, 0x30FF], // Katakana
+ [0x3100, 0x312F], // Bopomofo
+ [0x3130, 0x318F], // Hangul Compatibility Jamo
+ [0x3190, 0x319F], // Kanbun
+ [0x31A0, 0x31BF], // Bopomofo Extended
+ [0x3200, 0x32FF], // Enclosed CJK Letters and Months
+ [0x3300, 0x33FF], // CJK Compatibility
+ [0x3400, 0x4DB5], // CJK Unified Ideographs Extension A
+ [0x4E00, 0x9FFF], // CJK Unified Ideographs
+ [0xA000, 0xA48F], // Yi Syllables
+ [0xA490, 0xA4CF], // Yi Radicals
+ [0xAC00, 0xD7A3], // Hangul Syllables
+ [0xD800, 0xDB7F], // High Surrogates
+ [0xDB80, 0xDBFF], // High Private Use Surrogates
+ [0xDC00, 0xDFFF], // Low Surrogates
+ [0xE000, 0xF8FF], // Private Use
+ [0xF900, 0xFAFF], // CJK Compatibility Ideographs
+ [0xFB00, 0xFB4F], // Alphabetic Presentation Forms
+ [0xFB50, 0xFDFF], // Arabic Presentation Forms-A
+ [0xFE20, 0xFE2F], // Combining Half Marks
+ [0xFE30, 0xFE4F], // CJK Compatibility Forms
+ [0xFE50, 0xFE6F], // Small Form Variants
+ [0xFE70, 0xFEFE], // Arabic Presentation Forms-B
+ [0xFEFF, 0xFEFF], // Specials
+ [0xFF00, 0xFFEF], // Halfwidth and Fullwidth Forms
+ [0xFFF0, 0xFFFD] // Specials
+];
+
+function additionalComplexityForCharset(str, charset) {
+ for (var i = str.length - 1; i >= 0; i--) {
+ if (charset[0] <= str.charCodeAt(i) && str.charCodeAt(i) <= charset[1]) {
+ return charset[1] - charset[0] + 1;
+ }
+ }
+ return 0;
+}
+
+function inBanlist(str) {
+ if (options.banMode === 'strict') {
+ for (var i = 0; i < options.bannedPasswords.length; i++) {
+ if (options.bannedPasswords[i].indexOf(str) !== -1) {
+ return true;
+ }
+ }
+ return false;
+ }
+ else {
+ return (options.bannedPasswords.indexOf(str) > -1 ? true : false);
+ }
+}
+
+function evaluateSecurity(password) {
+ var complexity = 0,
+ valid = false;
+
+ // Reset complexity to 0 when banned password is found
+ if (!inBanlist(password)) {
+
+ // Add character complexity
+ for (var i = CHARSETS.length - 1; i >= 0; i--) {
+ complexity += additionalComplexityForCharset(password, CHARSETS[i]);
+ }
+
+ }
+ else {
+ complexity = 1;
+ }
+
+ // Use natural log to produce linear scale
+ complexity = Math.log(Math.pow(complexity, password.length)) * (1 / options.strengthScaleFactor);
+
+ valid = (complexity > MIN_COMPLEXITY && password.length >= options.minimumChars);
+
+ // Scale to percentage, so it can be used for a progress bar
+ complexity = (complexity / MAX_COMPLEXITY) * 100;
+ complexity = (complexity > 100) ? 100 : complexity;
+
+ return {
+ valid: valid,
+ complexity: complexity
+ };
+}
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/passwords.jsm
@@ -0,0 +1,79 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailPassword"];
+
+
+
+const EnigmailLazy = ChromeUtils.import("chrome://openpgp/content/modules/lazy.jsm").EnigmailLazy;
+const EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs.jsm").EnigmailPrefs;
+const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
+const subprocess = ChromeUtils.import("chrome://openpgp/content/modules/subprocess.jsm").subprocess;
+
+
+const gpgAgent = EnigmailLazy.loader("enigmail/gpgAgent.jsm", "EnigmailGpgAgent");
+const getDialog = EnigmailLazy.loader("enigmail/dialog.jsm", "EnigmailDialog");
+const getLocale = EnigmailLazy.loader("enigmail/locale.jsm", "EnigmailLocale");
+
+var EnigmailPassword = {
+ /*
+ * Get GnuPG command line options for receiving the password depending
+ * on the various user and system settings (gpg-agent/no passphrase)
+ *
+ * @return: Array the GnuPG command line options
+ */
+ command: function() {
+ return ["--use-agent"];
+ },
+
+ getMaxIdleMinutes: function() {
+ try {
+ return EnigmailPrefs.getPref("maxIdleMinutes");
+ }
+ catch (ex) {}
+
+ return 5;
+ },
+
+ clearPassphrase: function(win) {
+ // clear all passphrases from gpg-agent by reloading the config
+ if (!EnigmailCore.getService()) return;
+
+ let exitCode = -1;
+ let isError = 0;
+
+ const proc = {
+ command: gpgAgent().connGpgAgentPath,
+ arguments: [],
+ charset: null,
+ environment: EnigmailCore.getEnvList(),
+ stdin: function(pipe) {
+ pipe.write("RELOADAGENT\n");
+ pipe.write("/bye\n");
+ pipe.close();
+ },
+ stdout: function(data) {
+ if (data.search(/^ERR/m) >= 0) {
+ ++isError;
+ }
+ }
+ };
+
+ try {
+ exitCode = subprocess.call(proc).wait();
+ }
+ catch (ex) {}
+
+ if (isError === 0) {
+ getDialog().alert(win, getLocale().getString("passphraseCleared"));
+ }
+ else {
+ getDialog().alert(win, getLocale().getString("cannotClearPassphrase"));
+ }
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/persistentCrypto.jsm
@@ -0,0 +1,1129 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailPersistentCrypto"];
+
+const EnigmailLazy = ChromeUtils.import("chrome://openpgp/content/modules/lazy.jsm").EnigmailLazy;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailArmor = ChromeUtils.import("chrome://openpgp/content/modules/armor.jsm").EnigmailArmor;
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+const EnigmailExecution = ChromeUtils.import("chrome://openpgp/content/modules/execution.jsm").EnigmailExecution;
+const GlodaUtils = ChromeUtils.import("chrome://openpgp/content/modules/glodaUtils.jsm").GlodaUtils;
+const EnigmailCompat = ChromeUtils.import("chrome://openpgp/content/modules/compat.jsm").EnigmailCompat;
+const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
+const EnigmailGpg = ChromeUtils.import("chrome://openpgp/content/modules/gpg.jsm").EnigmailGpg;
+const EnigmailStreams = ChromeUtils.import("chrome://openpgp/content/modules/streams.jsm").EnigmailStreams;
+const EnigmailMime = ChromeUtils.import("chrome://openpgp/content/modules/mime.jsm").EnigmailMime;
+const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
+const EnigmailTimer = ChromeUtils.import("chrome://openpgp/content/modules/timer.jsm").EnigmailTimer;
+const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
+const jsmime = ChromeUtils.import("resource:///modules/jsmime.jsm").jsmime;
+const EnigmailStdlib = ChromeUtils.import("chrome://openpgp/content/modules/stdlib.jsm").EnigmailStdlib;
+const EnigmailEncryption = ChromeUtils.import("chrome://openpgp/content/modules/encryption.jsm").EnigmailEncryption;
+
+const getFixExchangeMsg = EnigmailLazy.loader("enigmail/fixExchangeMsg.jsm", "EnigmailFixExchangeMsg");
+const getDecryption = EnigmailLazy.loader("enigmail/decryption.jsm", "EnigmailDecryption");
+const getDialog = EnigmailLazy.loader("enigmail/dialog.jsm", "EnigmailDialog");
+const getGpgAgent = EnigmailLazy.loader("enigmail/gpgAgent.jsm", "EnigmailGpgAgent");
+
+const STATUS_OK = 0;
+const STATUS_FAILURE = 1;
+const STATUS_NOT_REQUIRED = 2;
+
+const IOSERVICE_CONTRACTID = "@mozilla.org/network/io-service;1";
+
+/*
+ * Decrypt a message and copy it to a folder
+ *
+ * @param nsIMsgDBHdr hdr Header of the message
+ * @param String destFolder Folder URI
+ * @param Boolean move If true the original message will be deleted
+ *
+ * @return a Promise that we do that
+ */
+var EnigmailPersistentCrypto = {
+
+ /***
+ * dispatchMessages
+ *
+ * Because Thunderbird throws all messages at once at us thus we have to rate limit the dispatching
+ * of the message processing. Because there is only a negligible performance gain when dispatching
+ * several message at once we serialize to not overwhelm low power devices.
+ *
+ * If targetFolder is null the message will be copied / moved in the same folder as the original
+ * message.
+ *
+ * If targetKey is not null the message will be encrypted again to the targetKey.
+ *
+ * The function is implemented asynchronously.
+ *
+ * Parameters
+ * aMsgHdrs: Array of nsIMsgDBHdr
+ * targetFolder: String; target folder URI or null
+ * copyListener: listener for async request (nsIMsgCopyServiceListener)
+ * move: Boolean: type of action; true = "move" / false = "copy"
+ * targetKey: KeyObject of target key if encryption is requested
+ *
+ **/
+
+ dispatchMessages: function(aMsgHdrs, targetFolder, copyListener, move, targetKey) {
+ EnigmailLog.DEBUG("persistentCrypto.jsm: dispatchMessages()\n");
+
+ let enigmailSvc = EnigmailCore.getService();
+ if (copyListener && !enigmailSvc) {
+ // could not initiate Enigmail - do nothing
+ copyListener.OnStopCopy(0);
+ return;
+ }
+
+ if (copyListener) {
+ copyListener.OnStartCopy();
+ }
+ let promise = EnigmailPersistentCrypto.cryptMessage(aMsgHdrs[0], targetFolder, move, targetKey);
+
+ let processNext = function(data) {
+ aMsgHdrs.splice(0, 1);
+ if (aMsgHdrs.length > 0) {
+ EnigmailPersistentCrypto.dispatchMessages(aMsgHdrs, targetFolder, copyListener, move, targetKey);
+ } else {
+ // last message was finished processing
+ if (copyListener) {
+ copyListener.OnStopCopy(0);
+ }
+ EnigmailLog.DEBUG("persistentCrypto.jsm: dispatchMessages - DONE\n");
+ }
+ };
+
+ promise.then(processNext);
+
+ promise.catch(function(err) {
+ processNext(null);
+ });
+ },
+
+ /***
+ * cryptMessage
+ *
+ * Decrypts a message. If targetKey is not null it
+ * encrypts a message to the target key afterwards.
+ *
+ * Parameters
+ * hdr: nsIMsgDBHdr of the message to encrypt
+ * destFolder: String; target folder URI
+ * move: Boolean: type of action; true = "move" / false = "copy"
+ * targetKey: KeyObject of target key if encryption is requested
+ **/
+ cryptMessage: function(hdr, destFolder, move, targetKey) {
+ return new Promise(
+ function(resolve, reject) {
+ let msgUriSpec = hdr.folder.getUriForMsg(hdr);
+ let msgUrl = EnigmailCompat.getUrlFromUriSpec(msgUriSpec);
+
+ const crypt = new CryptMessageIntoFolder(destFolder, move, resolve, targetKey);
+
+ try {
+ EnigmailMime.getMimeTreeFromUrl(msgUrl, true,
+ function f_(mime) {
+ crypt.messageParseCallback(mime, hdr);
+ });
+ } catch (ex) {
+ reject("msgHdrsDeleteoMimeMessage failed: " + ex.toString());
+ }
+ return;
+ }
+ );
+ }
+};
+
+function CryptMessageIntoFolder(destFolder, move, resolve, targetKey) {
+ this.destFolder = destFolder;
+ this.move = move;
+ this.resolve = resolve;
+ this.targetKey = targetKey;
+ this.messageDecrypted = false;
+
+ this.mimeTree = null;
+ this.decryptionTasks = [];
+ this.subject = "";
+}
+
+CryptMessageIntoFolder.prototype = {
+ messageParseCallback: async function(mimeTree, msgHdr) {
+ this.mimeTree = mimeTree;
+ this.hdr = msgHdr;
+
+ if (mimeTree.headers.has("subject")) {
+ this.subject = mimeTree.headers.get("subject");
+ }
+
+ await this.decryptMimeTree(mimeTree);
+
+ let msg = "";
+
+ // Encrypt the message if a target key is given.
+ if (this.targetKey) {
+ msg = this.encryptToKey(mimeTree);
+ if (!msg) {
+ // do nothing (still better than destroying the message)
+ this.resolve(true);
+ return;
+ } else {
+ this.messageDecrypted = true;
+ }
+ } else if (this.messageDecrypted) {
+ msg = this.mimeToString(mimeTree, true);
+ }
+
+ if (this.messageDecrypted) {
+ this.resolve(await this.storeMessage(msg));
+ } else
+ this.resolve(true);
+ },
+
+ encryptToKey: function(mimeTree) {
+ let exitCodeObj = {};
+ let statusFlagsObj = {};
+ let errorMsgObj = {};
+ EnigmailLog.DEBUG("persistentCrypto.jsm: Encrypting message.\n");
+
+
+ let inputMsg = this.mimeToString(mimeTree, false);
+
+
+ let encmsg = "";
+ try {
+ encmsg = EnigmailEncryption.encryptMessage(null,
+ 0,
+ inputMsg,
+ "0x" + this.targetKey.fpr,
+ "0x" + this.targetKey.fpr,
+ "",
+ EnigmailConstants.SEND_ENCRYPTED | EnigmailConstants.SEND_ALWAYS_TRUST,
+ exitCodeObj,
+ statusFlagsObj,
+ errorMsgObj
+ );
+ } catch (ex) {
+ EnigmailLog.DEBUG("persistentCrypto.jsm: Encryption failed: " + ex + "\n");
+ return null;
+ }
+
+ // Build the pgp-encrypted mime structure
+ let msg = "";
+
+ let rfc822Headers = []; // FIXME
+
+ // First the original headers
+ for (let header in rfc822Headers) {
+ if (header != "content-type" &&
+ header != "content-transfer-encoding" &&
+ header != "content-disposition") {
+ msg += prettyPrintHeader(header, rfc822Headers[header]) + "\n";
+ }
+ }
+ // Then multipart/encrypted ct
+ let boundary = EnigmailMime.createBoundary();
+ msg += "Content-Transfer-Encoding: 7Bit\n";
+ msg += "Content-Type: multipart/encrypted; ";
+ msg += "boundary=\"" + boundary + "\"; protocol=\"application/pgp-encrypted\"\n\n";
+ msg += "This is an OpenPGP/MIME encrypted message (RFC 4880 and 3156)\n";
+
+ // pgp-encrypted part
+ msg += "--" + boundary + "\n";
+ msg += "Content-Type: application/pgp-encrypted\n";
+ msg += "Content-Disposition: attachment\n";
+ msg += "Content-Transfer-Encoding: 7Bit\n\n";
+ msg += "Version: 1\n\n";
+
+ // the octet stream
+ msg += "--" + boundary + "\n";
+ msg += "Content-Type: application/octet-stream; name=\"encrypted.asc\"\n";
+ msg += "Content-Description: OpenPGP encrypted message\n";
+ msg += "Content-Disposition: inline; filename=\"encrypted.asc\"\n";
+ msg += "Content-Transfer-Encoding: 7Bit\n\n";
+ msg += encmsg;
+
+ // Bottom boundary
+ msg += "\n--" + boundary + "--\n";
+
+ // Fix up the line endings to be a proper dosish mail
+ msg = msg.replace(/\r/ig, "").replace(/\n/ig, "\r\n");
+
+ return msg;
+ },
+
+ /**
+ * Walk through the MIME message structure and decrypt the body if there is something to decrypt
+ */
+ decryptMimeTree: async function(mimePart) {
+ EnigmailLog.DEBUG("persistentCrypto.jsm: decryptMimeTree:\n");
+
+ if (this.isBrokenByExchange(mimePart)) {
+ this.fixExchangeMessage(mimePart);
+ }
+
+ if (this.isPgpMime(mimePart)) {
+ this.decryptPGPMIME(mimePart);
+ } else if (isAttachment(mimePart)) {
+ this.decryptAttachment(mimePart);
+ } else {
+ this.decryptINLINE(mimePart);
+ }
+
+ for (let i in mimePart.subParts) {
+ await this.decryptMimeTree(mimePart.subParts[i]);
+ }
+ },
+
+ /***
+ *
+ * Detect if mime part is PGP/MIME message that got modified by MS-Exchange:
+ *
+ * - multipart/mixed Container with
+ * - application/pgp-encrypted Attachment with name "PGPMIME Version Identification"
+ * - application/octet-stream Attachment with name "encrypted.asc" having the encrypted content in base64
+ * - see:
+ * - https://doesnotexist-openpgp-integration.thunderbird/forum/viewtopic.php?f=4&t=425
+ * - https://sourceforge.net/p/enigmail/forum/support/thread/4add2b69/
+ */
+
+ isBrokenByExchange: function(mime) {
+ EnigmailLog.DEBUG("persistentCrypto.jsm: isBrokenByExchange:\n");
+
+ try {
+ if (mime.subParts && mime.subParts.length === 3 &&
+ mime.fullContentType.toLowerCase().indexOf("multipart/mixed") >= 0 &&
+ mime.subParts[0].subParts.length === 0 &&
+ mime.subParts[0].fullContentType.search(/multipart\/encrypted/i) < 0 &&
+ mime.subParts[0].fullContentType.toLowerCase().indexOf("text/plain") >= 0 &&
+ mime.subParts[1].fullContentType.toLowerCase().indexOf("application/pgp-encrypted") >= 0 &&
+ mime.subParts[1].fullContentType.toLowerCase().search(/multipart\/encrypted/i) < 0 &&
+ mime.subParts[1].fullContentType.toLowerCase().search(/PGPMIME Versions? Identification/i) >= 0 &&
+ mime.subParts[2].fullContentType.toLowerCase().indexOf("application/octet-stream") >= 0 &&
+ mime.subParts[2].fullContentType.toLowerCase().indexOf("encrypted.asc") >= 0) {
+
+ EnigmailLog.DEBUG("persistentCrypto.jsm: isBrokenByExchange: found message broken by MS-Exchange\n");
+ return true;
+ }
+ } catch (ex) {}
+
+ return false;
+ },
+
+ isPgpMime: function(mimePart) {
+ EnigmailLog.DEBUG("persistentCrypto.jsm: isPgpMime()\n");
+
+ try {
+ if (mimePart.headers.has("content-type")) {
+ if (mimePart.headers.get("content-type").type.toLowerCase() === "multipart/encrypted" &&
+ mimePart.headers.get("content-type").get("protocol").toLowerCase() === "application/pgp-encrypted" &&
+ mimePart.subParts.length === 2) {
+ return true;
+ }
+ }
+ } catch (x) {}
+ return false;
+ },
+
+ decryptPGPMIME: async function(mimePart) {
+ EnigmailLog.DEBUG("persistentCrypto.jsm: decryptPGPMIME(" + mimePart.partNum + ")\n");
+
+ if (!mimePart.subParts[1]) throw "Not a correct PGP/MIME message";
+
+ const uiFlags = EnigmailConstants.UI_INTERACTIVE | EnigmailConstants.UI_UNVERIFIED_ENC_OK |
+ EnigmailConstants.UI_IGNORE_MDC_ERROR;
+ let exitCodeObj = {};
+ let statusFlagsObj = {};
+ let userIdObj = {};
+ let sigDetailsObj = {};
+ let errorMsgObj = {};
+ let keyIdObj = {};
+ let blockSeparationObj = {
+ value: ""
+ };
+ let encToDetailsObj = {};
+ var signatureObj = {};
+ signatureObj.value = "";
+
+
+ let data = getDecryption().decryptMessage(null, uiFlags, mimePart.subParts[1].body, signatureObj, exitCodeObj, statusFlagsObj,
+ keyIdObj, userIdObj, sigDetailsObj, errorMsgObj, blockSeparationObj, encToDetailsObj);
+
+ if (!data || data.length === 0) {
+ if (statusFlagsObj.value & EnigmailConstants.DISPLAY_MESSAGE) {
+ getDialog().alert(null, errorMsgObj.value);
+ throw "Decryption impossible";
+ }
+ }
+
+ EnigmailLog.DEBUG("persistentCrypto.jsm: analyzeDecryptedData: got " + data.length + " bytes\n");
+
+ if (EnigmailLog.getLogLevel() > 5) {
+ EnigmailLog.DEBUG("*** start data ***\n'" + data + "'\n***end data***\n");
+ }
+
+ if (data.length === 0) {
+ // fail if no data found
+ return;
+ }
+
+ let bodyIndex = data.search(/\n\s*\r?\n/);
+ if (bodyIndex < 0) {
+ bodyIndex = 0;
+ } else {
+ ++bodyIndex;
+ }
+
+ if (data.substr(bodyIndex).search(/\r?\n$/) === 0) {
+ return;
+ }
+
+ let m = Cc["@mozilla.org/messenger/mimeheaders;1"].createInstance(Ci.nsIMimeHeaders);
+ m.initialize(data.substr(0, bodyIndex));
+ let ct = m.extractHeader("content-type", false) || "";
+ let part = mimePart.partNum;
+
+ if (part.length > 0 && part.search(/[^01.]/) < 0) {
+ if (ct.search(/protected-headers/i) >= 0) {
+ if (m.hasHeader("subject")) {
+ let subject = m.extractHeader("subject", false) || "";
+ subject = subject.replace(/^(Re: )+/, "Re: ");
+ this.mimeTree.headers._rawHeaders.set("subject", [subject]);
+ }
+ } else if (this.mimeTree.headers.get("subject") === "p≡p") {
+ let subject = getPepSubject(data);
+ if (subject) {
+ subject = subject.replace(/^(Re: )+/, "Re: ");
+ this.mimeTree.headers._rawHeaders.set("subject", [subject]);
+ }
+ }
+ }
+
+ let boundary = getBoundary(mimePart);
+ if (!boundary)
+ boundary = EnigmailMime.createBoundary();
+
+ // append relevant headers
+ mimePart.headers.get("content-type").type = "multipart/mixed";
+ mimePart.headers._rawHeaders.set("content-type", ['multipart/mixed; boundary="' + boundary + '"']);
+ mimePart.subParts = [{
+ body: data,
+ decryptedPgpMime: true,
+ partNum: mimePart.partNum + ".1",
+ headers: {
+ _rawHeaders: new Map(),
+ get: function() {
+ return null;
+ },
+ has: function() {
+ return false;
+ }
+ },
+ subParts: []
+ }];
+
+
+ this.messageDecrypted = true;
+ },
+
+ decryptAttachment: function(mimePart) {
+
+ EnigmailLog.DEBUG("persistentCrypto.jsm: decryptAttachment()\n");
+ let attachmentHead = mimePart.body.substr(0, 30);
+ if (attachmentHead.search(/-----BEGIN PGP \w{5,10} KEY BLOCK-----/) >= 0) {
+ // attachment appears to be a PGP key file, we just go-a-head
+ return;
+ }
+
+ let attachmentName = getAttachmentName(mimePart);
+ attachmentName = attachmentName ? attachmentName.replace(/\.(pgp|asc|gpg)$/, "") : "";
+
+ let args = EnigmailGpg.getStandardArgs(true);
+ args.push("-d");
+
+ let statusMsgObj = {};
+ let cmdLineObj = {};
+ let exitCode = -1;
+ let statusFlagsObj = {
+ value: 0
+ };
+ let errorMsgObj = {};
+
+ let listener = EnigmailExecution.newSimpleListener((pipe) => {
+ pipe.write(mimePart.body);
+ pipe.close();
+ });
+
+ do {
+ // loop to allow for multiple tries of the passphrase
+ let proc = EnigmailExecution.execStart(getGpgAgent().agentPath, args, false, null, listener, statusFlagsObj);
+ if (!proc) {
+ return;
+ }
+ // Wait for child STDOUT to close
+ proc.wait();
+ EnigmailExecution.execEnd(listener, statusFlagsObj, statusMsgObj, cmdLineObj, errorMsgObj);
+
+ if ((listener.stdoutData && listener.stdoutData.length > 0) ||
+ (statusFlagsObj.value & EnigmailConstants.DECRYPTION_OKAY)) {
+ EnigmailLog.DEBUG("persistentCrypto.jsm: decryptAttachment: decryption OK\n");
+ exitCode = 0;
+ } else if (statusFlagsObj.value & (EnigmailConstants.DECRYPTION_FAILED | EnigmailConstants.MISSING_MDC)) {
+ EnigmailLog.DEBUG("persistentCrypto.jsm: decryptAttachment: decryption without MDC protection\n");
+ exitCode = 0;
+ } else if (statusFlagsObj.value & EnigmailConstants.DECRYPTION_FAILED) {
+ EnigmailLog.DEBUG("persistentCrypto.jsm: decryptAttachment: decryption failed\n");
+ // since we cannot find out if the user wants to cancel
+ // we should ask
+ let msg = EnigmailLocale.getString("converter.decryptAtt.failed", [attachmentName, this.subject]);
+
+ if (!getDialog().confirmDlg(null, msg,
+ EnigmailLocale.getString("dlg.button.retry"), EnigmailLocale.getString("dlg.button.skip"))) {
+ return;
+ }
+ } else if (statusFlagsObj.value & EnigmailConstants.DECRYPTION_INCOMPLETE) {
+ // failure; message not complete
+ EnigmailLog.DEBUG("persistentCrypto.jsm: decryptAttachment: decryption incomplete\n");
+ return;
+ } else {
+ // there is nothing to be decrypted
+ EnigmailLog.DEBUG("persistentCrypto.jsm: decryptAttachment: no decryption required\n");
+ return;
+ }
+
+ } while (exitCode !== 0);
+
+
+ EnigmailLog.DEBUG("persistentCrypto.jsm: decryptAttachment: decrypted to " + listener.stdoutData.length + " bytes\n");
+ if (statusFlagsObj.encryptedFileName && statusFlagsObj.encryptedFileName.length > 0) {
+ attachmentName = statusFlagsObj.encryptedFileName;
+ }
+
+ this.decryptedMessage = true;
+ mimePart.body = listener.stdoutData;
+ mimePart.headers._rawHeaders.set("content-disposition", `attachment; filename="${attachmentName}"`);
+ mimePart.headers._rawHeaders.set("content-transfer-encoding", ["base64"]);
+ let origCt = mimePart.headers.get("content-type");
+ let ct = origCt.type;
+
+
+ for (let i of origCt.entries()) {
+ if (i[0].toLowerCase() === "name") {
+ i[1] = i[1].replace(/\.(pgp|asc|gpg)$/, "");
+ }
+ ct += `; ${i[0]}="${i[1]}"`;
+ }
+
+ mimePart.headers._rawHeaders.set("content-type", [ct]);
+ },
+
+
+ decryptINLINE: function(mimePart) {
+ EnigmailLog.DEBUG("persistentCrypto.jsm: decryptINLINE()\n");
+
+ if (("decryptedPgpMime" in mimePart) && mimePart.decryptedPgpMime) {
+ return 0;
+ }
+
+ if ("body" in mimePart && mimePart.body.length > 0) {
+ let ct = getContentType(mimePart);
+
+ if (ct === "text/html") {
+ mimePart.body = this.stripHTMLFromArmoredBlocks(mimePart.body);
+ }
+
+ var exitCodeObj = {};
+ var statusFlagsObj = {};
+ var userIdObj = {};
+ var sigDetailsObj = {};
+ var errorMsgObj = {};
+ var keyIdObj = {};
+ var blockSeparationObj = {
+ value: ""
+ };
+ var encToDetailsObj = {};
+ var signatureObj = {};
+ signatureObj.value = "";
+
+ const uiFlags = EnigmailConstants.UI_INTERACTIVE | EnigmailConstants.UI_UNVERIFIED_ENC_OK |
+ EnigmailConstants.UI_IGNORE_MDC_ERROR;
+
+ var plaintexts = [];
+ var blocks = EnigmailArmor.locateArmoredBlocks(mimePart.body);
+ var tmp = [];
+
+ for (let i = 0; i < blocks.length; i++) {
+ if (blocks[i].blocktype == "MESSAGE") {
+ tmp.push(blocks[i]);
+ }
+ }
+
+ blocks = tmp;
+
+ if (blocks.length < 1) {
+ return 0;
+ }
+
+ let charset = "utf-8";
+
+ for (let i = 0; i < blocks.length; i++) {
+ let plaintext = null;
+ do {
+ let ciphertext = mimePart.body.substring(blocks[i].begin, blocks[i].end + 1);
+
+ if (ciphertext.length === 0) {
+ break;
+ }
+
+ let hdr = ciphertext.search(/(\r\r|\n\n|\r\n\r\n)/);
+ if (hdr > 0) {
+ let chset = ciphertext.substr(0, hdr).match(/^(charset:)(.*)$/mi);
+ if (chset && chset.length == 3) {
+ charset = chset[2].trim();
+ }
+ }
+ plaintext = getDecryption().decryptMessage(null, uiFlags, ciphertext, signatureObj, exitCodeObj, statusFlagsObj,
+ keyIdObj, userIdObj, sigDetailsObj, errorMsgObj, blockSeparationObj, encToDetailsObj);
+ if (!plaintext || plaintext.length === 0) {
+ if (statusFlagsObj.value & EnigmailConstants.DISPLAY_MESSAGE) {
+ getDialog().alert(null, errorMsgObj.value);
+ this.messageDecrypted = false;
+ return -1;
+ }
+
+ if (statusFlagsObj.value & (EnigmailConstants.DECRYPTION_FAILED | EnigmailConstants.MISSING_MDC)) {
+ EnigmailLog.DEBUG("persistentCrypto.jsm: decryptINLINE: no MDC protection, decrypting anyway\n");
+ }
+ if (statusFlagsObj.value & EnigmailConstants.DECRYPTION_FAILED) {
+ // since we cannot find out if the user wants to cancel
+ // we should ask
+ let msg = EnigmailLocale.getString("converter.decryptBody.failed", this.subject);
+
+ if (!getDialog().confirmDlg(null, msg,
+ EnigmailLocale.getString("dlg.button.retry"), EnigmailLocale.getString("dlg.button.skip"))) {
+ this.messageDecrypted = false;
+ return -1;
+ }
+ } else if (statusFlagsObj.value & EnigmailConstants.DECRYPTION_INCOMPLETE) {
+ this.messageDecrypted = false;
+ return -1;
+ } else {
+ plaintext = " ";
+ }
+ }
+
+ if (ct === "text/html") {
+ plaintext = plaintext.replace(/\n/ig, "<br/>\n");
+ }
+
+ let subject = "";
+ if (this.mimeTree.headers.has("subject")) {
+ subject = this.mimeTree.headers.get("subject");
+ }
+
+ if (i == 0 && this.mimeTree.headers.subject === "pEp" &&
+ mimePart.partNum.length > 0 && mimePart.partNum.search(/[^01.]/) < 0) {
+
+ let m = EnigmailMime.extractSubjectFromBody(plaintext);
+ if (m) {
+ plaintext = m.messageBody;
+ this.mimeTree.headers._rawHeaders.set("subject", [m.subject]);
+ }
+ }
+
+ if (plaintext) {
+ plaintexts.push(plaintext);
+ }
+ } while (!plaintext || plaintext === "");
+ }
+
+
+
+ var decryptedMessage = mimePart.body.substring(0, blocks[0].begin) + plaintexts[0];
+ for (let i = 1; i < blocks.length; i++) {
+ decryptedMessage += mimePart.body.substring(blocks[i - 1].end + 1, blocks[i].begin + 1) + plaintexts[i];
+ }
+
+ decryptedMessage += mimePart.body.substring(blocks[(blocks.length - 1)].end + 1);
+
+ // enable base64 encoding if non-ASCII character(s) found
+ let j = decryptedMessage.search(/[^\x01-\x7F]/); // eslint-disable-line no-control-regex
+ if (j >= 0) {
+ mimePart.headers._rawHeaders.set('content-transfer-encoding', ['base64']);
+ } else {
+ mimePart.headers._rawHeaders.set('content-transfer-encoding', ['8bit']);
+ }
+ mimePart.body = decryptedMessage;
+
+ let origCharset = getCharset(getHeaderValue(mimePart, 'content-type'));
+ if (origCharset) {
+ mimePart.headers_rawHeaders.set('content-type', getHeaderValue(mimePart, 'content-type').replace(origCharset, charset));
+ } else {
+ mimePart.headers._rawHeaders.set('content-type', getHeaderValue(mimePart, 'content-type') + "; charset=" + charset);
+ }
+
+ this.messageDecrypted = true;
+ return 1;
+ }
+
+ let ct = getContentType(mimePart);
+ EnigmailLog.DEBUG("persistentCrypto.jsm: Decryption skipped: " + ct + "\n");
+
+ return 0;
+ },
+
+ stripHTMLFromArmoredBlocks: function(text) {
+
+ var index = 0;
+ var begin = text.indexOf("-----BEGIN PGP");
+ var end = text.indexOf("-----END PGP");
+
+ while (begin > -1 && end > -1) {
+ let sub = text.substring(begin, end);
+
+ sub = sub.replace(/(<([^>]+)>)/ig, "");
+ sub = sub.replace(/&[A-z]+;/ig, "");
+
+ text = text.substring(0, begin) + sub + text.substring(end);
+
+ index = end + 10;
+ begin = text.indexOf("-----BEGIN PGP", index);
+ end = text.indexOf("-----END PGP", index);
+ }
+
+ return text;
+ },
+
+
+ /******
+ *
+ * We have the technology we can rebuild.
+ *
+ * Function to reassemble the message from the MIME Tree
+ * into a String.
+ *
+ ******/
+
+ mimeToString: function(mimePart, includeHeaders) {
+ EnigmailLog.DEBUG("persistentCrypto.jsm: mimeToString: part: '" + mimePart.partNum + "'\n");
+
+ let msg = "";
+ let rawHdr = mimePart.headers._rawHeaders;
+
+ if (includeHeaders && rawHdr.size > 0) {
+ for (let hdr of rawHdr.keys()) {
+ msg += formatMimeHeader(hdr, rawHdr.get(hdr)) + "\r\n";
+ }
+
+ msg += "\r\n";
+ }
+
+ if (mimePart.body.length > 0) {
+ let encoding = getTransferEncoding(mimePart);
+ if (!encoding) encoding = "8bit";
+
+ if (encoding === "quoted-printable") {
+ mimePart.headers._rawHeaders.set("content-transfer-encoding", ["base64"]);
+ encoding = "base64";
+ }
+
+ if (encoding === "base64") {
+ msg += EnigmailData.encodeBase64(mimePart.body);
+ } else {
+ msg += mimePart.body;
+ }
+
+ }
+
+ if (mimePart.subParts.length > 0) {
+ let boundary = EnigmailMime.getBoundary(rawHdr.get("content-type").join(""));
+
+ for (let i in mimePart.subParts) {
+ msg += `--${boundary}\r\n`;
+ msg += this.mimeToString(mimePart.subParts[i], true);
+ if (msg.search(/[\r\n]$/) < 0) {
+ msg += "\r\n";
+ }
+ }
+
+ msg += `--${boundary}--\r\n`;
+ }
+ return msg;
+ },
+
+ storeMessage: function(msg) {
+ let self = this;
+
+ return new Promise((resolve, reject) => {
+ //XXX Do we wanna use the tmp for this?
+ let tempFile = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties).get("TmpD", Ci.nsIFile);
+ tempFile.append("message.eml");
+ tempFile.createUnique(0, 384); // == 0600, octal is deprecated
+
+ // ensure that file gets deleted on exit, if something goes wrong ...
+ let extAppLauncher = Cc["@mozilla.org/mime;1"].getService(Ci.nsPIExternalAppLauncher);
+
+ let foStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
+ foStream.init(tempFile, 2, 0x200, false); // open as "write only"
+ foStream.write(msg, msg.length);
+ foStream.close();
+
+ extAppLauncher.deleteTemporaryFileOnExit(tempFile);
+
+ //
+ // This was taken from the HeaderToolsLite Example Addon "original by Frank DiLecce"
+ //
+ // this is interesting: nsIMsgFolder.copyFileMessage seems to have a bug on Windows, when
+ // the nsIFile has been already used by foStream (because of Windows lock system?), so we
+ // must initialize another nsIFile object, pointing to the temporary file
+ let fileSpec = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ fileSpec.initWithPath(tempFile.path);
+
+ const copySvc = Cc["@mozilla.org/messenger/messagecopyservice;1"].getService(Ci.nsIMsgCopyService);
+
+ let copyListener = {
+ QueryInterface: function(iid) {
+ if (iid.equals(Ci.nsIMsgCopyServiceListener) || iid.equals(Ci.nsISupports)) {
+ return this;
+ }
+ EnigmailLog.DEBUG("persistentCrypto.jsm: copyListener error\n");
+ throw Components.results.NS_NOINTERFACE;
+ },
+ GetMessageId: function(messageId) {},
+ OnProgress: function(progress, progressMax) {},
+ OnStartCopy: function() {
+ EnigmailLog.DEBUG("persistentCrypto.jsm: copyListener: OnStartCopy()\n");
+ },
+ SetMessageKey: function(key) {
+ EnigmailLog.DEBUG("persistentCrypto.jsm: copyListener: SetMessageKey(" + key + ")\n");
+ },
+ OnStopCopy: function(statusCode) {
+ EnigmailLog.DEBUG("persistentCrypto.jsm: copyListener: OnStopCopy()\n");
+ if (statusCode !== 0) {
+ EnigmailLog.DEBUG("persistentCrypto.jsm: Error copying message: " + statusCode + "\n");
+ try {
+ tempFile.remove(false);
+ } catch (ex) {
+ try {
+ fileSpec.remove(false);
+ } catch (e2) {
+ EnigmailLog.DEBUG("persistentCrypto.jsm: Could not delete temp file\n");
+ }
+ }
+ resolve(true);
+ return;
+ }
+ EnigmailLog.DEBUG("persistentCrypto.jsm: Copy complete\n");
+
+ if (self.move) {
+ deleteOriginalMail(self.hdr);
+ }
+
+ try {
+ tempFile.remove(false);
+ } catch (ex) {
+ try {
+ fileSpec.remove(false);
+ } catch (e2) {
+ EnigmailLog.DEBUG("persistentCrypto.jsm: Could not delete temp file\n");
+ }
+ }
+
+ EnigmailLog.DEBUG("persistentCrypto.jsm: Cave Johnson. We're done\n");
+ resolve(true);
+ }
+ };
+
+ EnigmailLog.DEBUG("persistentCrypto.jsm: copySvc ready for copy\n");
+ try {
+ if (self.mimeTree.headers.has("subject")) {
+ self.hdr.subject = self.mimeTree.headers.get("subject");
+ }
+ } catch (ex) {}
+
+ EnigmailCompat.copyFileToMailFolder(fileSpec, EnigmailCompat.getExistingFolder(self.destFolder),
+ 0, "", copyListener, null);
+ });
+ },
+
+ fixExchangeMessage: function(mimePart) {
+ EnigmailLog.DEBUG("persistentCrypto.jsm: fixExchangeMessage()\n");
+
+ const FixEx = getFixExchangeMsg();
+
+ let msg = this.mimeToString(mimePart, true);
+ let app = FixEx.determineCreatorApp(msg);
+
+ try {
+ let fixedMsg = FixEx.getRepairedMessage(msg);
+ let replacement = EnigmailMime.getMimeTree(fixedMsg, true);
+
+ for (let i in replacement) {
+ mimePart[i] = replacement[i];
+ }
+ } catch (ex) {}
+ }
+};
+
+
+/**
+ * Format a mime header
+ *
+ * e.g. content-type -> Content-Type
+ */
+
+function formatHeader(headerLabel) {
+ return headerLabel.replace(/^.|(-.)/g, function(match) {
+ return match.toUpperCase();
+ });
+}
+
+function formatMimeHeader(headerLabel, headerValue) {
+ if (Array.isArray(headerValue)) {
+ headerValue = headerValue.join("");
+ }
+ if (headerLabel.search(/^(sender|from|reply-to|to|cc|bcc)$/i) === 0) {
+ return formatHeader(headerLabel) + ": " + EnigmailMime.formatHeaderData(EnigmailMime.formatEmailAddress(headerValue));
+ } else {
+ return formatHeader(headerLabel) + ": " + EnigmailMime.formatHeaderData(EnigmailMime.encodeHeaderValue(headerValue));
+ }
+}
+
+
+function prettyPrintHeader(headerLabel, headerData) {
+ let hdrData = "";
+ if (Array.isArray(headerData)) {
+ let h = [];
+ for (let i in headerData) {
+ h.push(formatMimeHeader(headerLabel, GlodaUtils.deMime(headerData[i])));
+ }
+ return h.join("\r\n");
+ } else {
+ return formatMimeHeader(headerLabel, GlodaUtils.deMime(String(headerData)));
+ }
+}
+
+function getHeaderValue(mimeStruct, header) {
+ EnigmailLog.DEBUG("persistentCrypto.jsm: getHeaderValue: '" + header + "'\n");
+
+ try {
+ if (mimeStruct.headers.has(header)) {
+ let hdrVal = mimeStruct.headers.get(header);
+ if (typeof hdrVal == "string") {
+ return hdrVal;
+ } else {
+ return mimeStruct.headers[header].join(" ");
+ }
+ } else {
+ return "";
+ }
+ } catch (ex) {
+ EnigmailLog.DEBUG("persistentCrypto.jsm: getHeaderValue: header not present\n");
+ return "";
+ }
+}
+
+/***
+ * get the formatted headers for MimeMessage objects
+ *
+ * @headerArr: Array of headers (key/value pairs), such as mime.headers
+ * @ignoreHeadersArr: Array of headers to exclude from returning
+ *
+ * @return: String containing formatted header list
+ */
+function getRfc822Headers(headerArr, contentType, ignoreHeadersArr) {
+ let hdrs = "";
+
+ let ignore = [];
+ if (contentType.indexOf("multipart/") >= 0) {
+ ignore = ['content-transfer-encoding',
+ 'content-disposition',
+ 'content-description'
+ ];
+ }
+
+ if (ignoreHeadersArr) {
+ ignore = ignore.concat(ignoreHeadersArr);
+ }
+
+ for (let i in headerArr) {
+ if (ignore.indexOf(i) < 0) {
+ hdrs += prettyPrintHeader(i, headerArr[i]) + "\r\n";
+ }
+ }
+
+ return hdrs;
+}
+
+function getContentType(mime) {
+ try {
+ if (mime && ("headers" in mime) && mime.headers.has("content-type")) {
+ return mime.headers.get("content-type").type.toLowerCase();
+ }
+ } catch (e) {
+ EnigmailLog.DEBUG("persistentCrypto.jsm: getContentType: " + e + "\n");
+ }
+ return null;
+}
+
+// return the content of the boundary parameter
+function getBoundary(mime) {
+ try {
+ if (mime && ("headers" in mime) && mime.headers.has("content-type")) {
+ return mime.headers.get("content-type").get("boundary");
+ }
+ } catch (e) {
+ EnigmailLog.DEBUG("persistentCrypto.jsm: getBoundary: " + e + "\n");
+ }
+ return null;
+}
+
+function getCharset(mime) {
+ try {
+ if (mime && ("headers" in mime) && mime.headers.has("content-type")) {
+ let c = mime.headers.get("content-type").get("charset");
+ if (c) return c.toLowerCase();
+ }
+ } catch (e) {
+ EnigmailLog.DEBUG("persistentCrypto.jsm: getCharset: " + e + "\n");
+ }
+ return null;
+}
+
+function getProtocol(mime) {
+ try {
+ if (mime && ("headers" in mime) && mime.headers.has("content-type")) {
+ let c = mime.headers.get("content-type").get("protocol");
+ if (c) return c.toLowerCase();
+ }
+ } catch (e) {
+ EnigmailLog.DEBUG("persistentCrypto.jsm: getProtocol: " + e + "\n");
+ }
+ return "";
+}
+
+function getTransferEncoding(mime) {
+ try {
+ if (mime && ("headers" in mime) && mime.headers._rawHeaders.has("content-transfer-encoding")) {
+ let c = mime.headers._rawHeaders.get("content-transfer-encoding")[0];
+ if (c) return c.toLowerCase();
+ }
+ } catch (e) {
+ EnigmailLog.DEBUG("persistentCrypto.jsm: getTransferEncoding: " + e + "\n");
+ }
+ return "8Bit";
+}
+
+function isAttachment(mime) {
+ try {
+ if (mime && ("headers" in mime)) {
+ if (mime.fullContentType.search(/^multipart\//i) === 0) return false;
+ if (mime.fullContentType.search(/^text\//i) < 0) return true;
+
+ if (mime.headers.has("content-disposition")) {
+ let c = mime.headers.get("content-disposition")[0];
+ if (c) {
+ if (c.search(/^attachment/i) === 0) {
+ return true;
+ }
+ }
+ }
+ }
+ } catch (x) {}
+ return false;
+}
+
+
+function getAttachmentName(mime) {
+ try {
+ if (mime && ("headers" in mime) && mime.headers.has("content-disposition")) {
+ let c = mime.headers.get("content-disposition")[0];
+ if (c) {
+ if (c.search(/^attachment/i) === 0) {
+ return EnigmailMime.getParameter(c, "filename");
+ }
+ }
+ }
+ } catch (e) {
+ EnigmailLog.DEBUG("persistentCrypto.jsm: getAttachmentName: " + e + "\n");
+ }
+ return null;
+}
+
+
+function getPepSubject(mimeString) {
+ EnigmailLog.DEBUG("persistentCrypto.jsm: getPepSubject()\n");
+
+ let subject = null;
+
+ let emitter = {
+ ct: "",
+ firstPlainText: false,
+ startPart: function(partNum, headers) {
+ EnigmailLog.DEBUG("persistentCrypto.jsm: getPepSubject.startPart: partNum=" + partNum + "\n");
+ try {
+ this.ct = String(headers.getRawHeader("content-type")).toLowerCase();
+ if (!subject && !this.firstPlainText) {
+ let s = headers.getRawHeader("subject");
+ if (s) {
+ subject = String(s);
+ this.firstPlainText = true;
+ }
+ }
+ } catch (ex) {
+ this.ct = "";
+ }
+ },
+
+ endPart: function(partNum) {},
+
+ deliverPartData: function(partNum, data) {
+ EnigmailLog.DEBUG("persistentCrypto.jsm: getPepSubject.deliverPartData: partNum=" + partNum + " ct=" + this.ct + "\n");
+ if (!this.firstPlainText && this.ct.search(/^text\/plain/) === 0) {
+ // check data
+ this.firstPlainText = true;
+
+ let o = EnigmailMime.extractSubjectFromBody(data);
+ if (o) {
+ subject = o.subject;
+ }
+ }
+ }
+ };
+
+ let opt = {
+ strformat: "unicode",
+ bodyformat: "decode"
+ };
+
+ try {
+ let p = new jsmime.MimeParser(emitter, opt);
+ p.deliverData(mimeString);
+ } catch (ex) {}
+
+ return subject;
+}
+
+/**
+ * Lazy deletion of original messages
+ */
+function deleteOriginalMail(msgHdr) {
+ EnigmailLog.DEBUG("persistentCrypto.jsm: deleteOriginalMail(" + msgHdr.messageKey + ")\n");
+
+ let delMsg = function() {
+ try {
+ EnigmailLog.DEBUG("persistentCrypto.jsm: deleting original message " + msgHdr.messageKey + "\n");
+ EnigmailStdlib.msgHdrsDelete([msgHdr]);
+ } catch (e) {
+ EnigmailLog.DEBUG("persistentCrypto.jsm: deletion failed. Error: " + e.toString() + "\n");
+ }
+ };
+
+ EnigmailTimer.setTimeout(delMsg, 500);
+}
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/pgpmimeHandler.jsm
@@ -0,0 +1,302 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * Module for handling PGP/MIME encrypted and/or signed messages
+ * implemented as an XPCOM object
+ */
+
+const {
+ manager: Cm,
+ results: Cr,
+ Constructor: CC
+} = Components;
+Cm.QueryInterface(Ci.nsIComponentRegistrar);
+
+
+var EXPORTED_SYMBOLS = ["EnigmailPgpmimeHander"];
+
+const EnigmailCompat = ChromeUtils.import("chrome://openpgp/content/modules/compat.jsm").EnigmailCompat;
+const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailMimeDecrypt = ChromeUtils.import("chrome://openpgp/content/modules/mimeDecrypt.jsm").EnigmailMimeDecrypt;
+const EnigmailVerify = ChromeUtils.import("chrome://openpgp/content/modules/mimeVerify.jsm").EnigmailVerify;
+const EnigmailWksMimeHandler = ChromeUtils.import("chrome://openpgp/content/modules/wksMimeHandler.jsm").EnigmailWksMimeHandler;
+const EnigmailMime = ChromeUtils.import("chrome://openpgp/content/modules/mime.jsm").EnigmailMime;
+const EnigmailSingletons = ChromeUtils.import("chrome://openpgp/content/modules/singletons.jsm").EnigmailSingletons;
+
+const PGPMIME_JS_DECRYPTOR_CONTRACTID = "@mozilla.org/mime/pgp-mime-js-decrypt;1";
+const PGPMIME_JS_DECRYPTOR_CID = Components.ID("{7514cbeb-2bfd-4b2c-829b-1a4691fa0ac8}");
+
+////////////////////////////////////////////////////////////////////
+// handler for PGP/MIME encrypted and PGP/MIME signed messages
+// data is processed from libmime -> nsPgpMimeProxy
+
+var gConv;
+var inStream;
+
+var gLastEncryptedUri = "";
+
+const throwErrors = {
+ onDataAvailable: function() {
+ throw "error";
+ },
+ onStartRequest: function() {
+ throw "error";
+ },
+ onStopRequest: function() {
+ throw "error";
+ }
+};
+
+/**
+ * UnknownProtoHandler is a default handler for unkonwn protocols. It ensures that the
+ * signed message part is always displayed without any further action.
+ */
+function UnknownProtoHandler() {
+ if (!gConv) {
+ gConv = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
+ }
+
+ if (!inStream) {
+ inStream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream);
+ }
+}
+
+UnknownProtoHandler.prototype = {
+ onStartRequest: function(request, ctxt) {
+ this.mimeSvc = request.QueryInterface(Ci.nsIPgpMimeProxy);
+ if (!("outputDecryptedData" in this.mimeSvc)) {
+ this.mimeSvc.onStartRequest(null, ctxt);
+ }
+ this.bound = EnigmailMime.getBoundary(this.mimeSvc.contentType);
+ /*
+ readMode:
+ 0: before message
+ 1: inside message
+ 2: afer message
+ */
+ this.readMode = 0;
+ },
+
+ onDataAvailable: function(p1, p2, p3, p4) {
+ if (EnigmailCompat.isMessageUriInPgpMime()) {
+ this.processData(p1, p2, p3, p4);
+ }
+ else {
+ this.processData(p1, null, p2, p3, p4);
+ }
+ },
+
+ processData: function (req, stream, offset, count) {
+ if (count > 0) {
+ inStream.init(stream);
+ let data = inStream.read(count);
+ let l = data.replace(/\r\n/g, "\n").split(/\n/);
+
+ if (data.search(/\n$/) >= 0) {
+ l.pop();
+ }
+
+ let startIndex = 0;
+ let endIndex = l.length;
+
+ if (this.readMode < 2) {
+ for (let i = 0; i < l.length; i++) {
+ if (l[i].indexOf("--") === 0 && l[i].indexOf(this.bound) === 2) {
+ ++this.readMode;
+ if (this.readMode === 1) {
+ startIndex = i + 1;
+ }
+ else if (this.readMode === 2) {
+ endIndex = i - 1;
+ }
+ }
+ }
+
+ if (this.readMode >= 1 && startIndex < l.length) {
+ let out = l.slice(startIndex, endIndex).join("\n") + "\n";
+
+ if ("outputDecryptedData" in this.mimeSvc) {
+ // TB >= 57
+ this.mimeSvc.outputDecryptedData(out, out.length);
+ }
+ else {
+ gConv.setData(out, out.length);
+ this.mimeSvc.onDataAvailable(null, null, gConv, 0, out.length);
+ }
+ }
+ }
+ }
+ },
+
+ onStopRequest: function() {
+ if (!("outputDecryptedData" in this.mimeSvc)) {
+ this.mimeSvc.onStopRequest(null, 0);
+ }
+ }
+};
+
+function PgpMimeHandler() {
+
+ EnigmailLog.DEBUG("pgpmimeHandler.js: PgpMimeHandler()\n"); // always log this one
+
+}
+
+PgpMimeHandler.prototype = {
+ classDescription: "Enigmail JS Decryption Handler",
+ classID: PGPMIME_JS_DECRYPTOR_CID,
+ contractID: PGPMIME_JS_DECRYPTOR_CONTRACTID,
+ QueryInterface: EnigmailCompat.generateQI([Ci.nsIStreamListener]),
+ inStream: Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream),
+
+ onStartRequest: function(request, ctxt) {
+ let mimeSvc = request.QueryInterface(Ci.nsIPgpMimeProxy);
+ let ct = mimeSvc.contentType;
+
+ let uri = null;
+ if ("messageURI" in mimeSvc) {
+ uri = mimeSvc.messageURI;
+ }
+ else {
+ uri = ctxt;
+ }
+
+ if (!EnigmailCore.getService()) {
+ // Ensure Enigmail is initialized
+ if (ct.search(/application\/(x-)?pkcs7-signature/i) > 0) {
+ return this.handleSmime(uri);
+ }
+ return null;
+ }
+
+ EnigmailLog.DEBUG("pgpmimeHandler.js: onStartRequest\n");
+ EnigmailLog.DEBUG("pgpmimeHandler.js: ct= " + ct + "\n");
+
+ let cth = null;
+
+ if (ct.search(/^multipart\/encrypted/i) === 0) {
+ if (uri) {
+ let u = uri.QueryInterface(Ci.nsIURI);
+ gLastEncryptedUri = u.spec;
+ }
+ // PGP/MIME encrypted message
+
+ cth = EnigmailMimeDecrypt.newPgpMimeHandler();
+ }
+ else if (ct.search(/^multipart\/signed/i) === 0) {
+ if (ct.search(/application\/pgp-signature/i) > 0) {
+ // PGP/MIME signed message
+ cth = EnigmailVerify.newVerifier();
+ }
+ else if (ct.search(/application\/(x-)?pkcs7-signature/i) > 0) {
+ let lastUriSpec = "";
+ if (uri) {
+ let u = uri.QueryInterface(Ci.nsIURI);
+ lastUriSpec = u.spec;
+ }
+ // S/MIME signed message
+ if (lastUriSpec !== gLastEncryptedUri && EnigmailVerify.lastMsgWindow) {
+ // if message is displayed then handle like S/MIME message
+ return this.handleSmime(uri);
+ }
+ else {
+ // otherwise just make sure message body is returned
+ cth = EnigmailVerify.newVerifier("application/(x-)?pkcs7-signature");
+ }
+ }
+ }
+ else if (ct.search(/application\/vnd.gnupg.wks/i) === 0) {
+ cth = EnigmailWksMimeHandler.newHandler();
+ }
+
+ if (!cth) {
+ EnigmailLog.ERROR("pgpmimeHandler.js: unknown protocol for content-type: " + ct + "\n");
+ cth = new UnknownProtoHandler();
+ }
+
+ if (cth) {
+ this.onDataAvailable = cth.onDataAvailable.bind(cth);
+ this.onStopRequest = cth.onStopRequest.bind(cth);
+ return cth.onStartRequest(request, uri);
+ }
+
+ return null;
+ },
+
+ onDataAvailable: function(req, stream, offset, count) {},
+
+ onStopRequest: function(request, status) {},
+
+ handleSmime: function(uri) {
+ this.contentHandler = throwErrors;
+
+ if (uri) {
+ uri = uri.QueryInterface(Ci.nsIURI);
+ }
+
+ let headerSink = EnigmailSingletons.messageReader;
+ headerSink.handleSMimeMessage(uri);
+ },
+
+ getMessengerWindow: function() {
+ let windowManager = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
+
+ let winEnum = windowManager.getEnumerator(null);
+
+ while (winEnum.hasMoreElements()) {
+ let thisWin = winEnum.getNext();
+ if (thisWin.location.href.search(/\/messenger.xul$/) > 0) {
+ return thisWin;
+ }
+ }
+
+ return null;
+ }
+};
+
+
+class Factory {
+ constructor(component) {
+ this.component = component;
+ this.register();
+ Object.freeze(this);
+ }
+
+ createInstance(outer, iid) {
+ if (outer) {
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+ }
+ return new this.component();
+ }
+
+ register() {
+ Cm.registerFactory(this.component.prototype.classID,
+ this.component.prototype.classDescription,
+ this.component.prototype.contractID,
+ this);
+ }
+
+ unregister() {
+ Cm.unregisterFactory(this.component.prototype.classID, this);
+ }
+}
+
+
+var EnigmailPgpmimeHander = {
+ startup: function(reason) {
+ try {
+ this.factory = new Factory(PgpMimeHandler);
+ }
+ catch (ex) {}
+ },
+
+ shutdown: function(reason) {
+ if (this.factory) {
+ this.factory.unregister();
+ }
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/pipeConsole.jsm
@@ -0,0 +1,33 @@
+/*jshint -W097 */
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailConsole"];
+
+const MAX_SIZE = 32768;
+var dataCache = "";
+var gotNewData = false;
+
+var EnigmailConsole = {
+ write: function(data) {
+ dataCache += data;
+ if (dataCache.length > MAX_SIZE) {
+ dataCache = dataCache.substr(-MAX_SIZE, MAX_SIZE);
+ }
+ gotNewData = true;
+ },
+
+ hasNewData: function() {
+ return gotNewData;
+ },
+
+ getData: function() {
+ gotNewData = false;
+ return dataCache;
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/prefs.jsm
@@ -0,0 +1,320 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailPrefs"];
+
+
+
+
+
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
+const {
+ Services
+} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+const ENIGMAIL_PREFS_ROOT = "temp.openpgp.";
+
+const p = {
+ service: null,
+ branch: null,
+ root: null,
+ defaultBranch: null
+};
+
+function initPrefService() {
+ try {
+ p.service = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService);
+
+ p.root = p.service.getBranch(null);
+ p.branch = p.service.getBranch(ENIGMAIL_PREFS_ROOT);
+ p.defaultBranch = p.service.getDefaultBranch(null);
+
+ try {
+ if (p.branch.getCharPref("logDirectory")) {
+ EnigmailLog.setLogLevel(5);
+ }
+ }
+ catch (ex) {} // don't log anythign if accessing logDirectory fails
+ }
+ catch (ex) {
+ EnigmailLog.ERROR("prefs.jsm: Error in instantiating PrefService\n");
+ EnigmailLog.ERROR(ex.toString());
+ }
+}
+
+
+var gPrefs = {};
+
+/**
+ * Load a preference default value
+ * This function is called while loading defaultPrefs.js
+ */
+function pref(key, val) {
+ gPrefs[key] = val;
+}
+
+/**
+ * Load default preferences for bootstrapped addon
+ */
+function setDefaultPrefs() {
+ EnigmailLog.DEBUG("prefs.jsm: setDefaultPrefs()\n");
+
+ Services.scriptloader.loadSubScript("chrome://openpgp/content/preferences/defaultPrefs.js", {}, "UTF-8");
+
+ let branch = p.defaultBranch;
+ for (let key in gPrefs) {
+ try {
+ let val = gPrefs[key];
+ switch (typeof val) {
+ case "boolean":
+ branch.setBoolPref(key, val);
+ break;
+ case "number":
+ branch.setIntPref(key, val);
+ break;
+ case "string":
+ branch.setCharPref(key, val);
+ break;
+ }
+ }
+ catch(ex) {
+ EnigmailLog.ERROR(`prefs.jsm: setDefaultPrefs(${key}: ERROR ${ex.toString()}\n`);
+ }
+ }
+}
+
+
+var EnigmailPrefs = {
+ startup: function(reason) {
+ try {
+ initPrefService();
+ setDefaultPrefs();
+ }
+ catch (ex) {
+ EnigmailLog.ERROR("prefs.jsm: Error while loading default prefs: " + ex.message + "\n");
+ }
+ },
+
+ getPrefRoot: function() {
+ if (!p.branch) {
+ initPrefService();
+ }
+
+ return p.root;
+ },
+
+ getPrefBranch: function() {
+ if (!p.branch) {
+ initPrefService();
+ }
+
+ return p.branch;
+ },
+
+ getPref: function(prefName) {
+ if (!p.branch) {
+ initPrefService();
+ }
+
+ var prefValue = null;
+ try {
+ var prefType = p.branch.getPrefType(prefName);
+ // Get pref value
+ switch (prefType) {
+ case p.branch.PREF_BOOL:
+ prefValue = p.branch.getBoolPref(prefName);
+ break;
+ case p.branch.PREF_INT:
+ prefValue = p.branch.getIntPref(prefName);
+ break;
+ case p.branch.PREF_STRING:
+ prefValue = p.branch.getCharPref(prefName);
+ break;
+ default:
+ prefValue = undefined;
+ break;
+ }
+ }
+ catch (ex) {
+ // Failed to get pref value
+ EnigmailLog.ERROR("prefs.jsm: getPref: unknown prefName:" + prefName + " \n");
+ }
+
+ return prefValue;
+ },
+
+ /**
+ * Store a user preference.
+ *
+ * @param String prefName An identifier.
+ * @param any value The value to be stored. Allowed types: Boolean OR Integer OR String.
+ *
+ * @return Boolean Was the value stored successfully?
+ */
+ setPref: function(prefName, value) {
+ EnigmailLog.DEBUG("prefs.jsm: setPref: " + prefName + ", " + value + "\n");
+
+ if (!p.branch) {
+ initPrefService();
+ }
+
+ // Discover the type of the preference, as stored in the user preferences.
+ // If the preference identifier doesn't exist yet, it returns 0. In that
+ // case the type depends on the argument "value".
+ var prefType;
+ prefType = p.branch.getPrefType(prefName);
+ if (prefType === 0) {
+ switch (typeof value) {
+ case "boolean":
+ prefType = p.branch.PREF_BOOL;
+ break;
+ case "number":
+ prefType = p.branch.PREF_INT;
+ break;
+ case "string":
+ prefType = p.branch.PREF_STRING;
+ break;
+ default:
+ prefType = 0;
+ break;
+ }
+ }
+ var retVal = false;
+
+ // Save the preference only and if only the type is bool, int or string.
+ switch (prefType) {
+ case p.branch.PREF_BOOL:
+ p.branch.setBoolPref(prefName, value);
+ retVal = true;
+ break;
+
+ case p.branch.PREF_INT:
+ p.branch.setIntPref(prefName, value);
+ retVal = true;
+ break;
+
+ case p.branch.PREF_STRING:
+ p.branch.setCharPref(prefName, value);
+ retVal = true;
+ break;
+
+ default:
+ break;
+ }
+
+ return retVal;
+ },
+
+ /**
+ * Save the Mozilla preferences file (prefs.js)
+ *
+ * no return value
+ */
+ savePrefs: function() {
+ EnigmailLog.DEBUG("prefs.jsm: savePrefs\n");
+ try {
+ p.service.savePrefFile(null);
+ }
+ catch (ex) {}
+ },
+
+ /**
+ * Compiles all Enigmail preferences into an object
+ */
+ getAllPrefs: function() {
+ EnigmailLog.DEBUG("prefs.js: getAllPrefs\n");
+
+ var retObj = {
+ value: 0
+ };
+ var branch = this.getPrefBranch();
+ var allPrefs = branch.getChildList("", retObj);
+ var prefObj = {};
+ var nsIPB = Components.interfaces.nsIPrefBranch;
+
+ for (var q in allPrefs) {
+ var name = allPrefs[q];
+
+ /*
+ * agentPath is system-depend, configuredVersion build-depend and
+ * advancedUser must be set in order to save the profile.
+ */
+ if (name == "agentPath" || name == "configuredVersion") {
+ continue;
+ }
+
+ switch (branch.getPrefType(name)) {
+ case nsIPB.PREF_STRING:
+ prefObj[name] = branch.getCharPref(name);
+ break;
+ case nsIPB.PREF_INT:
+ prefObj[name] = branch.getIntPref(name);
+ break;
+ case nsIPB.PREF_BOOL:
+ prefObj[name] = branch.getBoolPref(name);
+ break;
+ default:
+ EnigmailLog.ERROR("Pref '" + name + "' has unknown type\n");
+ }
+ }
+
+ return prefObj;
+ },
+
+ /**
+ * register a listener to listen to a change in the Enigmail preferences.
+ *
+ * @param prefName: String - name of Enigmail preference
+ * @param observerFunc: Function - callback function to be triggered
+ *
+ * @return Object: observer object (to be used to deregister the observer)
+ */
+ registerPrefObserver: function(prefName, observerFunc) {
+ EnigmailLog.DEBUG("prefs.jsm: registerPrefObserver(" + prefName + ")\n");
+ let branch = this.getPrefRoot();
+
+ let observer = {
+ observe: function(aSubject, aTopic, aData) {
+ try {
+ if (String(aData) == ENIGMAIL_PREFS_ROOT + this.prefName) {
+ EnigmailLog.DEBUG("prefs.jsm: preference observed: " + aData + "\n");
+ observerFunc();
+ }
+ }
+ catch (ex) {}
+ },
+
+ prefName: prefName,
+
+ QueryInterface: function(iid) {
+ if (iid.equals(Ci.nsIObserver) ||
+ iid.equals(Ci.nsISupportsWeakReference) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+
+ throw Components.results.NS_NOINTERFACE;
+ }
+ };
+ branch.addObserver(ENIGMAIL_PREFS_ROOT, observer, false);
+ return observer;
+ },
+
+ /**
+ * de-register an observer created by registerPrefObserver().
+ *
+ * @param observer: Object - observer object returned by registerPrefObserver
+ */
+ unregisterPrefObserver(observer) {
+ EnigmailLog.DEBUG("prefs.jsm: unregisterPrefObserver(" + observer.prefName + ")\n");
+
+ let branch = this.getPrefRoot();
+
+ branch.removeObserver(ENIGMAIL_PREFS_ROOT, observer);
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/protocolHandler.jsm
@@ -0,0 +1,194 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailProtocolHandler"];
+
+const EnigmailCompat = ChromeUtils.import("chrome://openpgp/content/modules/compat.jsm").EnigmailCompat;
+const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
+const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailStreams = ChromeUtils.import("chrome://openpgp/content/modules/streams.jsm").EnigmailStreams;
+const EnigmailURIs = ChromeUtils.import("chrome://openpgp/content/modules/uris.jsm").EnigmailURIs;
+const EnigmailKeyRing = ChromeUtils.import("chrome://openpgp/content/modules/keyRing.jsm").EnigmailKeyRing;
+const NetUtil = ChromeUtils.import("resource://gre/modules/NetUtil.jsm").NetUtil;
+
+const NS_ENIGMAILPROTOCOLHANDLER_CONTRACTID = "@mozilla.org/network/protocol;1?name=enigmail";
+const NS_ENIGMAILPROTOCOLHANDLER_CID = Components.ID("{847b3a11-7ab1-11d4-8f02-006008948af5}");
+const ASS_CONTRACTID = "@mozilla.org/appshell/appShellService;1";
+const WMEDIATOR_CONTRACTID = "@mozilla.org/appshell/window-mediator;1";
+
+const nsIProtocolHandler = Ci.nsIProtocolHandler;
+
+var EC = EnigmailCore;
+
+const gDummyPKCS7 =
+ 'Content-Type: multipart/mixed;\r\n boundary="------------060503030402050102040303\r\n\r\nThis is a multi-part message in MIME format.\r\n--------------060503030402050102040303\r\nContent-Type: application/x-pkcs7-mime\r\nContent-Transfer-Encoding: 8bit\r\n\r\n\r\n--------------060503030402050102040303\r\nContent-Type: application/x-enigmail-dummy\r\nContent-Transfer-Encoding: 8bit\r\n\r\n\r\n--------------060503030402050102040303--\r\n';
+
+
+function EnigmailProtocolHandler() {}
+
+EnigmailProtocolHandler.prototype = {
+ classDescription: "Enigmail Protocol Handler",
+ classID: NS_ENIGMAILPROTOCOLHANDLER_CID,
+ contractID: NS_ENIGMAILPROTOCOLHANDLER_CONTRACTID,
+ scheme: "enigmail",
+ defaultPort: -1,
+ protocolFlags: nsIProtocolHandler.URI_INHERITS_SECURITY_CONTEXT |
+ nsIProtocolHandler.URI_LOADABLE_BY_ANYONE |
+ nsIProtocolHandler.URI_NORELATIVE |
+ nsIProtocolHandler.URI_NOAUTH |
+ nsIProtocolHandler.URI_OPENING_EXECUTES_SCRIPT,
+
+ QueryInterface: EnigmailCompat.generateQI([nsIProtocolHandler]),
+
+ newURI: function(aSpec, originCharset, aBaseURI) {
+ EnigmailLog.DEBUG("protocolHandler.jsm: EnigmailProtocolHandler.newURI: aSpec='" + aSpec + "'\n");
+
+ // cut of any parameters potentially added to the URI; these cannot be handled
+ if (aSpec.substr(0, 14) == "enigmail:dummy") aSpec = "enigmail:dummy";
+
+ let uri;
+
+ try {
+ uri = Cc["@mozilla.org/network/simple-uri;1"].createInstance(Ci.nsIURI);
+ } catch (x) {
+ uri = NetUtil.newURI("data:text/plain,enigmail");
+ }
+
+ try {
+ // TB <= 58
+ uri.spec = aSpec;
+ } catch (x) {
+ aSpec = aSpec.substr(9);
+ let i = aSpec.indexOf("?");
+ try {
+ // TB < 60
+ uri.scheme = "enigmail";
+ if (i >= 0) {
+ uri.query = aSpec.substr(i + 1);
+ uri.pathQueryRef = aSpec.substr(0, i);
+ } else {
+ uri.pathQueryRef = aSpec;
+ }
+ } catch (ex) {
+ uri = uri.mutate().setScheme("enigmail").finalize();
+ if (i >= 0) {
+ uri = uri.mutate().setQuery(aSpec.substr(i + 1)).finalize();
+ uri = uri.mutate().setPathQueryRef(aSpec.substr(0, i)).finalize();
+ } else {
+ uri = uri.mutate().setPathQueryRef(aSpec).finalize();
+ }
+ }
+
+ }
+
+ return uri;
+ },
+
+ newChannel: function(aURI, loadInfo) {
+ EnigmailLog.DEBUG("protocolHandler.jsm: EnigmailProtocolHandler.newChannel: URI='" + aURI.spec + "'\n");
+
+ var messageId = EnigmailData.extractMessageId(aURI.spec);
+ var mimeMessageId = EnigmailData.extractMimeMessageId(aURI.spec);
+ var contentType, contentCharset, contentData;
+
+ if (messageId) {
+ // Handle enigmail:message/...
+
+ if (!EC.getEnigmailService()) {
+ throw Components.results.NS_ERROR_FAILURE;
+ }
+
+ if (EnigmailURIs.getMessageURI(messageId)) {
+ var messageUriObj = EnigmailURIs.getMessageURI(messageId);
+
+ contentType = messageUriObj.contentType;
+ contentCharset = messageUriObj.contentCharset;
+ contentData = messageUriObj.contentData;
+
+ EnigmailLog.DEBUG("protocolHandler.jsm: EnigmailProtocolHandler.newChannel: messageURL=" + messageUriObj.originalUrl + ", content length=" + contentData.length + ", " + contentType + ", " +
+ contentCharset + "\n");
+
+ // do NOT delete the messageUriObj now from the list, this will be done once the message is unloaded (fix for bug 9730).
+
+ } else if (mimeMessageId) {
+ this.handleMimeMessage(mimeMessageId);
+ } else {
+
+ contentType = "text/plain";
+ contentCharset = "";
+ contentData = "Enigmail error: invalid URI " + aURI.spec;
+ }
+
+ let channel = EnigmailStreams.newStringChannel(aURI, contentType, "UTF-8", contentData, loadInfo);
+
+
+ return channel;
+ }
+
+ if (aURI.spec == aURI.scheme + ":dummy") {
+ // Dummy PKCS7 content (to access mimeEncryptedClass)
+ return EnigmailStreams.newStringChannel(aURI, "message/rfc822", "", gDummyPKCS7, loadInfo);
+ }
+
+ var winName, spec;
+ if (aURI.spec == "about:" + aURI.scheme) {
+ // About Enigmail
+ // winName = "about:"+enigmail;
+ winName = "about:enigmail";
+ spec = "chrome://openpgp/content/ui/enigmailAbout.xul";
+
+ } else if (aURI.spec == aURI.scheme + ":console") {
+ // Display enigmail console messages
+ winName = "enigmail:console";
+ spec = "chrome://openpgp/content/ui/enigmailConsole.xul";
+
+ } else if (aURI.spec == aURI.scheme + ":keygen") {
+ // Display enigmail key generation console
+ winName = "enigmail:keygen";
+ spec = "chrome://openpgp/content/ui/enigmailKeygen.xul";
+
+ } else {
+ // Display Enigmail about page
+ winName = "about:enigmail";
+ spec = "chrome://openpgp/content/ui/enigmailAbout.xul";
+ }
+
+ var windowManager = Cc[WMEDIATOR_CONTRACTID].getService(Ci.nsIWindowMediator);
+
+ var winEnum = windowManager.getEnumerator(null);
+ var recentWin = null;
+ while (winEnum.hasMoreElements() && !recentWin) {
+ var thisWin = winEnum.getNext();
+ if (thisWin.location.href == spec) {
+ recentWin = thisWin;
+ }
+ }
+
+ if (recentWin) {
+ recentWin.focus();
+ } else {
+ var appShellSvc = Cc[ASS_CONTRACTID].getService(Ci.nsIAppShellService);
+ var domWin = appShellSvc.hiddenDOMWindow;
+
+ domWin.open(spec, "_blank", "chrome,menubar,toolbar,resizable");
+ }
+
+ throw Components.results.NS_ERROR_FAILURE;
+ },
+
+ handleMimeMessage: function(messageId) {
+ // EnigmailLog.DEBUG("protocolHandler.jsm: EnigmailProtocolHandler.handleMimeMessage: messageURL="+messageUriObj.originalUrl+", content length="+contentData.length+", "+contentType+", "+contentCharset+"\n");
+ EnigmailLog.DEBUG("protocolHandler.jsm: EnigmailProtocolHandler.handleMimeMessage: messageURL=, content length=, , \n");
+ },
+
+ allowPort: function(port, scheme) {
+ // non-standard ports are not allowed
+ return false;
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/rng.jsm
@@ -0,0 +1,63 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+const EXPORTED_SYMBOLS = ["EnigmailRNG"];
+
+Components.utils.importGlobalProperties(["crypto"]);
+
+/**
+ * Create a string of random characters of the set A-Z a-z 0-9 with numChars length,
+ * using the browser crypto API that gets cryptographically strong random values
+ *
+ * @param numChar: Number - the length of the string to return
+ *
+ * @return String
+ */
+function generateRandomString(numChars) {
+
+ // Map of characters that are allowed to be returned
+ const charMap = new Array("A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
+ "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
+ "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
+ "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
+ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9");
+
+ const charMapLength = charMap.length; // 62 for the set A-Z a-z 0-9
+
+ let randNumArray = new Uint16Array(numChars);
+ crypto.getRandomValues(randNumArray);
+
+ let randomString = "";
+
+ for (let i = 0; i < numChars; i++) {
+ // compute the modulo to get numbers between 0 and (charMapLength - 1)
+ // Uint16 range 65536 modulo 62 is only 2, this minimal statistical imbalance is acceptable
+ let modulo = randNumArray[i] % charMapLength;
+
+ randomString += charMap[modulo];
+ }
+
+ return randomString;
+}
+
+
+/**
+ * Generates a random UInt32 for use in randomising key selection and wait times between refreshing keys.
+ *
+ * @return random UInt32
+ */
+function generateRandomUint32() {
+ let randomNumber = new Uint32Array(1);
+ crypto.getRandomValues(randomNumber);
+ return randomNumber[0];
+}
+
+var EnigmailRNG = {
+ generateRandomUint32: generateRandomUint32,
+ generateRandomString: generateRandomString
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/rules.jsm
@@ -0,0 +1,605 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailRules"];
+
+
+/*EnigmailFuncs: false, : false, : false, : false, : false */
+const EnigmailFuncs = ChromeUtils.import("chrome://openpgp/content/modules/funcs.jsm").EnigmailFuncs;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailOS = ChromeUtils.import("chrome://openpgp/content/modules/os.jsm").EnigmailOS;
+const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
+const EnigmailApp = ChromeUtils.import("chrome://openpgp/content/modules/app.jsm").EnigmailApp;
+const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
+const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
+const EnigmailDialog = ChromeUtils.import("chrome://openpgp/content/modules/dialog.jsm").EnigmailDialog;
+const EnigmailLazy = ChromeUtils.import("chrome://openpgp/content/modules/lazy.jsm").EnigmailLazy;
+
+const getKeyRing = EnigmailLazy.loader("enigmail/keyRing.jsm", "EnigmailKeyRing");
+
+const NS_RDONLY = 0x01;
+const NS_WRONLY = 0x02;
+const NS_CREATE_FILE = 0x08;
+const NS_TRUNCATE = 0x20;
+const DEFAULT_FILE_PERMS = 0o600;
+
+const rulesListHolder = {
+ rulesList: null
+};
+
+var EnigmailRules = {
+
+ getRulesFile: function() {
+ EnigmailLog.DEBUG("rules.jsm: getRulesFile()\n");
+ var rulesFile = EnigmailApp.getProfileDirectory();
+ rulesFile.append("pgprules.xml");
+ return rulesFile;
+ },
+
+ loadRulesFile: function() {
+ var flags = NS_RDONLY;
+ var rulesFile = this.getRulesFile();
+ if (rulesFile.exists()) {
+ var fileContents = EnigmailFiles.readFile(rulesFile);
+
+ return this.loadRulesFromString(fileContents);
+ }
+
+ return false;
+ },
+
+ loadRulesFromString: function(contents) {
+ EnigmailLog.DEBUG("rules.jsm: loadRulesFromString()\n");
+ if (contents.length === 0 || contents.search(/^\s*$/) === 0) {
+ return false;
+ }
+
+ var domParser;
+ try {
+ domParser = new DOMParser();
+ } catch (ex) {
+ domParser = Cc["@mozilla.org/xmlextras/domparser;1"].createInstance(Ci.nsIDOMParser);
+ }
+ rulesListHolder.rulesList = domParser.parseFromString(contents, "text/xml");
+
+ return true;
+ },
+
+ saveRulesFile: function() {
+ EnigmailLog.DEBUG("rules.jsm: saveRulesFile()\n");
+
+ var flags = NS_WRONLY | NS_CREATE_FILE | NS_TRUNCATE;
+ var domSerializer;
+
+ try {
+ domSerializer = new XMLSerializer();
+ } catch (ex) {
+ domSerializer = Cc["@mozilla.org/xmlextras/xmlserializer;1"].createInstance(Ci.nsIDOMSerializer);
+ }
+
+ var rulesFile = this.getRulesFile();
+ if (rulesFile) {
+ if (rulesListHolder.rulesList) {
+ // the rule list is not empty -> write into file
+ return EnigmailFiles.writeFileContents(rulesFile.path,
+ domSerializer.serializeToString(rulesListHolder.rulesList.firstChild),
+ DEFAULT_FILE_PERMS);
+ } else {
+ // empty rule list -> delete rules file
+ try {
+ rulesFile.remove(false);
+ } catch (ex) {}
+ return true;
+ }
+ } else {
+ return false;
+ }
+ },
+
+ getRulesData: function(rulesListObj) {
+ EnigmailLog.DEBUG("rules.jsm: getRulesData()\n");
+
+ var ret = true;
+
+ if (!rulesListHolder.rulesList) {
+ ret = this.loadRulesFile();
+ }
+
+ if (rulesListHolder.rulesList) {
+ rulesListObj.value = rulesListHolder.rulesList;
+ return ret;
+ }
+
+ rulesListObj.value = null;
+ return false;
+ },
+
+ /**
+ * Create new rule
+ *
+ * @param appendToEnd: Boolean - true: append rule at the end of the rules list
+ * false: insert rule at the start of the rules list
+ * @param toAddress: String - Adress(es) to match. Multiple email addresses are separated by spaces.
+ * The matching is done on substrings, with curly brackets ({}) defining substring boundaries:
+ * "{" is equivalent to ^ in regexp
+ * "}" is equivalent to $ in regexp
+ * @param keyList: String - space separated list of key IDs (starting with 0x)
+ * If keyList === ".", use the email address
+ * @param sign: Number - 0/1/2 as defined below
+ * @param encrypt: Number - 0/1/2 as defined below
+ * @param pgpMime: Number - 0/1/2 as defined below
+ * @param flags: Number - 0: no flags / 1: negate rule
+ *
+ * sign/encrypt/pgpMime values:
+ * 0: Disable the action (= "Never")
+ * 1: Use the setting in Message Composition
+ * 2: Enable the action (= "Always")
+ */
+ addRule: function(appendToEnd, toAddress, keyList, sign, encrypt, pgpMime, flags) {
+ EnigmailLog.DEBUG("rules.jsm: addRule()\n");
+ var domParser;
+ if (!rulesListHolder.rulesList) {
+ try {
+ domParser = new DOMParser();
+ } catch (ex) {
+ domParser = Cc["@mozilla.org/xmlextras/domparser;1"].createInstance(Ci.nsIDOMParser);
+ }
+
+ rulesListHolder.rulesList = domParser.parseFromString("<pgpRuleList/>", "text/xml");
+ }
+ var negate = (flags & 1);
+ var rule = rulesListHolder.rulesList.createElement("pgpRule");
+ rule.setAttribute("email", toAddress.toLowerCase());
+ rule.setAttribute("keyId", keyList);
+ rule.setAttribute("sign", sign);
+ rule.setAttribute("encrypt", encrypt);
+ rule.setAttribute("pgpMime", pgpMime);
+ rule.setAttribute("negateRule", flags);
+ var origFirstChild = rulesListHolder.rulesList.firstChild.firstChild;
+
+ if (origFirstChild && (!appendToEnd)) {
+ rulesListHolder.rulesList.firstChild.insertBefore(rule, origFirstChild);
+ rulesListHolder.rulesList.firstChild.insertBefore(rulesListHolder.rulesList.createTextNode(EnigmailOS.isDosLike ? "\r\n" : "\n"), origFirstChild);
+ } else {
+ rulesListHolder.rulesList.firstChild.appendChild(rule);
+ rulesListHolder.rulesList.firstChild.appendChild(rulesListHolder.rulesList.createTextNode(EnigmailOS.isDosLike ? "\r\n" : "\n"));
+ }
+ },
+
+ /**
+ * Create new rule or update existing rule if the rule already exists.
+ * The key to decide if the rule exists is the email address (must match 1:1)
+ *
+ * @param ruleObj: Object with attributes {keyList, sign, encrypt, pgpMime, flags}
+ * @return: Number: 0 - no update / 1 - rule updated / 2 - new rule created
+ */
+ insertOrUpdateRule: function(ruleObj) {
+ if ((!("email" in ruleObj)) || ruleObj.email.length === 0) return 0;
+
+ let node = this.getRuleByEmail(ruleObj.email);
+
+ if (node) {
+ node.setAttribute("keyId", ruleObj.keyList);
+ node.setAttribute("sign", ruleObj.sign);
+ node.setAttribute("encrypt", ruleObj.encrypt);
+ node.setAttribute("pgpMime", ruleObj.pgpMime);
+ node.setAttribute("negateRule", ruleObj.flags);
+ this.saveRulesFile();
+
+ return 1;
+ }
+
+ // no rule matched, let's add the rule at the start of the list
+ this.addRule(false, ruleObj.email, ruleObj.keyList, ruleObj.sign, ruleObj.encrypt, ruleObj.pgpMime, ruleObj.flags);
+ this.saveRulesFile();
+
+ return 2;
+ },
+
+
+ /**
+ * Get a rule if it matches exactly one email address
+ *
+ * @param emailAddr: String - emailAddress to search
+ *
+ * @return Object: node object (DOM object)
+ */
+ getRuleByEmail: function(emailAddr) {
+ emailAddr = emailAddr.toLowerCase();
+
+ if (emailAddr.search(/^\{.*\}$/) < 0) {
+ emailAddr = "{" + emailAddr + "}";
+ }
+
+ let rulesListObj = {};
+ this.getRulesData(rulesListObj);
+ let rulesList = rulesListObj.value;
+
+ if (rulesList) {
+ for (let node = rulesList.firstChild.firstChild; node; node = node.nextSibling) {
+ if (node.tagName == "pgpRule") {
+ try {
+ let nodeEmail = node.getAttribute("email");
+ if (!nodeEmail) {
+ continue;
+ }
+ if (nodeEmail.toLowerCase() === emailAddr) {
+ return node;
+ }
+ } catch (ex) {
+ EnigmailLog.DEBUG("rules.jsm: getRuleByEmail(): ignore exception: " + ex.description + "\n");
+ }
+ }
+ }
+ }
+
+ return null;
+ },
+
+ clearRules: function() {
+ rulesListHolder.rulesList = null;
+ },
+
+ DEBUG_EmailList: function(name, list) {
+ EnigmailLog.DEBUG(" " + name + ":\n");
+ for (let i = 0; i < list.length; i++) {
+ let elem = list[i];
+ let str = " [" + i + "]: ";
+ if (elem.orig) {
+ str += "orig: '" + elem.orig + "' ";
+ }
+ if (elem.addr) {
+ str += "addr: '" + elem.addr + "' ";
+ }
+ if (elem.keys) {
+ str += "keys: '" + elem.keys + "' ";
+ }
+ EnigmailLog.DEBUG(str + "\n");
+ }
+ },
+
+ /**
+ * process resulting sign/encryp/pgpMime mode for passed string of email addresses and
+ * use rules and interactive rule dialog to replace emailAddrsStr by known keys
+ * Input parameters:
+ * @emailAddrsStr: comma and space separated string of addresses to process
+ * @startDialogForMissingKeys: true: start dialog for emails without key(s)
+ * Output parameters:
+ * @matchedKeysObj.value: comma separated string of matched keys AND email addresses for which no key was found (or "")
+ * @matchedKeysObj.addrKeysList: all email/keys mappings (array of objects with addr as string and keys as comma separated string)
+ * (does NOT contain emails for which no key was found)
+ * @matchedKeysObj.addrNoKeyList: list of emails that don't have a key according to rules
+ * @flagsObj: return value for combined sign/encrype/pgpMime mode
+ * values might be: 0='never', 1='maybe', 2='always', 3='conflict'
+ *
+ * @return: false if error occurred or processing was canceled
+ */
+ mapAddrsToKeys: function(emailAddrsStr, startDialogForMissingKeys, window,
+ matchedKeysObj, flagsObj) {
+ EnigmailLog.DEBUG("rules.jsm: mapAddrsToKeys(): emailAddrsStr=\"" + emailAddrsStr + "\" startDialogForMissingKeys=" + startDialogForMissingKeys + "\n");
+
+ let enigmailSvc = EnigmailCore.getService();
+ if (!enigmailSvc) {
+ EnigmailLog.DEBUG("EnigmailCore Service is down\n");
+ return false;
+ }
+
+ // initialize return value and the helper variables for them:
+ matchedKeysObj.value = "";
+ flagsObj.value = false;
+ let flags = {}; // object to be able to modify flags in subfunction
+ flags.sign = EnigmailConstants.ENIG_UNDEF; // default sign flag is: maybe
+ flags.encrypt = EnigmailConstants.ENIG_UNDEF; // default encrypt flag is: maybe
+ flags.pgpMime = EnigmailConstants.ENIG_UNDEF; // default pgpMime flag is: maybe
+
+ // create openList: list of addresses not processed by rules yet
+ // - each entry has
+ // - orig: the original full email address
+ // - addr: the lowercased pure email address to check against rules and keys
+ // - elements will be moved
+ // - to addrKeysList if a matching rule with keys was found
+ // - to addrNoKeyList if a rule with "do not process further rules" ("." as key) applies
+ let emailAddrList = ("," + emailAddrsStr + ",").split(/\s*,\s*/);
+
+ let openList = [];
+ for (let i = 0; i < emailAddrList.length; ++i) {
+ let orig = emailAddrList[i];
+ if (orig) {
+ let addr = null;
+ try {
+ addr = EnigmailFuncs.stripEmail(orig.toLowerCase());
+ } catch (ex) {}
+ if (addr) {
+ let elem = {
+ orig: orig,
+ addr: addr
+ };
+ openList.push(elem);
+ }
+ }
+ }
+ //this.DEBUG_EmailList("openList", openList);
+ let addrKeysList = []; // NEW: list of found email addresses and their associated keys
+ let addrNoKeyList = []; // NEW: final list of email addresses that have no key according to rules
+
+ // process recipient rules
+ let rulesListObj = {};
+ if (this.getRulesData(rulesListObj)) {
+
+ let rulesList = rulesListObj.value;
+ if (rulesList.firstChild.nodeName == "parsererror") {
+ EnigmailDialog.alert(window, "Invalid pgprules.xml file:\n" + rulesList.firstChild.textContent);
+ return false;
+ }
+ EnigmailLog.DEBUG("rules.jsm: mapAddrsToKeys(): rules successfully loaded; now process them\n");
+
+ // go through all rules to find match with email addresses
+ // - note: only if the key field has a value, an address is done with processing
+ for (let node = rulesList.firstChild.firstChild; node; node = node.nextSibling) {
+ if (node.tagName == "pgpRule") {
+ try {
+ let rule = {};
+ rule.email = node.getAttribute("email");
+ if (!rule.email) {
+ continue;
+ }
+ rule.negate = false;
+ if (node.getAttribute("negateRule")) {
+ rule.negate = Number(node.getAttribute("negateRule"));
+ }
+ if (!rule.negate) {
+ rule.keyId = node.getAttribute("keyId");
+ rule.sign = node.getAttribute("sign");
+ rule.encrypt = node.getAttribute("encrypt");
+ rule.pgpMime = node.getAttribute("pgpMime");
+ this.mapRuleToKeys(rule,
+ openList, flags, addrKeysList, addrNoKeyList, false);
+ }
+ // no negate rule handling (turned off in dialog)
+ } catch (ex) {
+ EnigmailLog.DEBUG("rules.jsm: mapAddrsToKeys(): ignore exception: " + ex.description + "\n");
+ }
+ }
+ }
+
+ // go again through the list to find autocrypt:// prefixed rules
+ for (let node = rulesList.firstChild.firstChild; node; node = node.nextSibling) {
+ if (node.tagName == "pgpRule") {
+ try {
+ let rule = {};
+ rule.email = node.getAttribute("email");
+ if (!rule.email) {
+ continue;
+ }
+ rule.negate = false;
+ if (node.getAttribute("negateRule")) {
+ rule.negate = Number(node.getAttribute("negateRule"));
+ }
+ if (!rule.negate) {
+ rule.keyId = node.getAttribute("keyId");
+ rule.sign = node.getAttribute("sign");
+ rule.encrypt = node.getAttribute("encrypt");
+ rule.pgpMime = node.getAttribute("pgpMime");
+ this.mapRuleToKeys(rule,
+ openList, flags, addrKeysList, addrNoKeyList, true);
+ }
+ // no negate rule handling (turned off in dialog)
+ } catch (ex) {
+ EnigmailLog.DEBUG("rules.jsm: mapAddrsToKeys(): ignore exception: " + ex.description + "\n");
+ }
+ }
+ }
+
+ }
+
+ // NOTE: here we have
+ // - openList: the addresses not having any key assigned yet
+ // (and not marked as don't process any other rule)
+ // - addresses with "don't process other rules" are in addrNoKeyList
+ //this.DEBUG_EmailList("openList", openList);
+ //this.DEBUG_EmailList("addrKeysList", addrKeysList);
+ //this.DEBUG_EmailList("addrnoKeyList", addrnoKeyList);
+
+ // if requested: start dialog to add new rule for each missing key
+ if (startDialogForMissingKeys) {
+ let inputObj = {};
+ let resultObj = {};
+ for (let i = 0; i < openList.length; i++) {
+ let theAddr = openList[i].addr;
+ // start dialog only if the email address contains a @ or no 0x at the beginning:
+ // - reason: newsgroups have neither @ nor 0x
+ if (theAddr.indexOf("@") != -1 || theAddr.indexOf("0x") !== 0) {
+ inputObj.toAddress = "{" + theAddr + "}";
+ inputObj.options = "";
+ inputObj.command = "add";
+ window.openDialog("chrome://openpgp/content/ui/enigmailSingleRcptSettings.xul", "",
+ "dialog,modal,centerscreen,resizable", inputObj, resultObj);
+ if (resultObj.cancelled === true) {
+ return false;
+ }
+
+ if (!resultObj.negate) {
+ this.mapRuleToKeys(resultObj,
+ openList, flags, addrKeysList, addrNoKeyList, false);
+ }
+ // no negate rule handling (turned off in dialog)
+ }
+ }
+ }
+
+ // return value of OLD interface:
+ // IFF we found keys, return keys AND unprocessed addresses in matchedKeysObj.value as comma-separated string
+ if (addrKeysList.length > 0) {
+ let tmpList = addrKeysList.concat(addrNoKeyList).concat(openList);
+ matchedKeysObj.value = tmpList[0].keys;
+ for (let idx = 1; idx < tmpList.length; ++idx) {
+ if (tmpList[idx].keys) {
+ matchedKeysObj.value += ", " + tmpList[idx].keys;
+ } else {
+ matchedKeysObj.value += ", " + tmpList[idx].addr;
+ }
+ }
+ // sort key list and make it unique?
+ }
+
+ // return value of NEW interface:
+ // return
+ // - in matchedKeysObj.addrKeysList: found email/keys mappings (array of objects with addr and keys)
+ // - in matchedKeysObj.addrNoKeyList: list of unprocessed emails
+ matchedKeysObj.addrKeysList = addrKeysList;
+ if (openList.length > 0) {
+ matchedKeysObj.addrNoKeyList = addrNoKeyList.concat(openList);
+ } else {
+ matchedKeysObj.addrNoKeyList = addrNoKeyList;
+ }
+
+ // return result from combining flags
+ flagsObj.sign = flags.sign;
+ flagsObj.encrypt = flags.encrypt;
+ flagsObj.pgpMime = flags.pgpMime;
+ flagsObj.value = true;
+
+ EnigmailLog.DEBUG(" found keys:\n");
+ for (let i = 0; i < matchedKeysObj.addrKeysList.length; i++) {
+ EnigmailLog.DEBUG(" " + matchedKeysObj.addrKeysList[i].addr + ": " + matchedKeysObj.addrKeysList[i].keys + "\n");
+ }
+ EnigmailLog.DEBUG(" addresses without keys:\n");
+ for (let i = 0; i < matchedKeysObj.addrNoKeyList.length; i++) {
+ EnigmailLog.DEBUG(" " + matchedKeysObj.addrNoKeyList[i].addr + "\n");
+ }
+ EnigmailLog.DEBUG(" old returned value:\n");
+ EnigmailLog.DEBUG(" " + matchedKeysObj.value + "\n");
+
+ return true;
+ },
+
+ mapRuleToKeys: function(rule, openList, flags, addrKeysList, addrNoKeyList, isAutocryptEmail = false) {
+ //EnigmailLog.DEBUG("rules.jsm: mapRuleToKeys() rule.email='" + rule.email + "'\n");
+ let ruleList = rule.email.toLowerCase().split(/[ ,;]+/);
+ for (let ruleIndex = 0; ruleIndex < ruleList.length; ++ruleIndex) {
+ let ruleEmailElem = ruleList[ruleIndex]; // ruleEmailElem has format such as '{name@qqq.de}' or '@qqq' or '{name' or '@qqq.de}'
+ //EnigmailLog.DEBUG(" process ruleElem: '" + ruleEmailElem + "'\n");
+ for (let openIndex = 0; openIndex < openList.length; ++openIndex) {
+ let addr = openList[openIndex].addr;
+ // search with { and } around because these are used as start and end markers in the rules:
+ let idx;
+
+ if (isAutocryptEmail) {
+ idx = ('{' + EnigmailConstants.AC_RULE_PREFIX + addr + '}').indexOf(ruleEmailElem);
+ } else {
+ idx = ('{' + addr + '}').indexOf(ruleEmailElem);
+ }
+ if (idx >= 0) {
+ if (ruleEmailElem == rule.email) {
+ EnigmailLog.DEBUG("rules.jsm: mapRuleToKeys(): for '" + addr + "' ('" + openList[openIndex].orig +
+ "') found matching rule element '" + ruleEmailElem + "'\n");
+ } else {
+ EnigmailLog.DEBUG("rules.jsm: mapRuleToKeys(): for '" + addr + "' ('" + openList[openIndex].orig +
+ "') found matching rule element '" + ruleEmailElem + "' from '" + rule.email + "'\n");
+ }
+
+
+
+ // process rule:
+ // NOTE: rule.keyId might be:
+ // - keys: => assign keys to all matching emails
+ // and mark matching address as no longer open
+ // - ".": signals "Do not check further rules for the matching address"
+ // => mark all matching address as no longer open, but assign no keys
+ // (thus, add it to the addrNoKeyList)
+ // - empty: Either if "Continue with next rule for the matching address"
+ // OR: if "Use the following OpenPGP keys:" with no keys and
+ // warning (will turn off encryption) acknowledged
+ // => then we only process the flags
+
+ if (rule.keyId) {
+ if (isAutocryptEmail) {
+ let keyObj = getKeyRing().getKeyById(rule.keyId);
+ if (keyObj) {
+ if (!(keyObj.getEncryptionValidity().keyValid)) {
+ keyObj = null;
+ deleteAutocryptRule(addr);
+ }
+ }
+
+ if (!keyObj) continue;
+ }
+
+ // move found address from openAdresses to corresponding list (with keys added)
+ let elem = openList.splice(openIndex, 1)[0];
+ --openIndex; // IMPORTANT because we remove element in the array we iterate on
+ if (rule.keyId != ".") {
+ // keys exist: assign keys as comma-separated string
+ let ids = rule.keyId.replace(/[ ,;]+/g, ", ");
+ elem.keys = ids;
+ addrKeysList.push(elem);
+ } else {
+ // '.': no further rule processing and no key: addr was (finally) processed but without any key
+ addrNoKeyList.push(elem);
+ }
+ }
+
+ // process sign/encrypt/ppgMime settings
+ flags.sign = this.combineFlagValues(flags.sign, Number(rule.sign));
+ flags.encrypt = this.combineFlagValues(flags.encrypt, Number(rule.encrypt));
+ flags.pgpMime = this.combineFlagValues(flags.pgpMime, Number(rule.pgpMime));
+
+ }
+ }
+ }
+ },
+
+ /**
+ * check for the attribute of type "sign"/"encrypt"/"pgpMime" of the passed node
+ * and combine its value with oldVal and check for conflicts
+ * values might be: 0='never', 1='maybe', 2='always', 3='conflict'
+ * @oldVal: original input value
+ * @newVal: new value to combine with
+ * @return: result value after applying the rule (0/1/2)
+ * and combining it with oldVal
+ */
+ combineFlagValues: function(oldVal, newVal) {
+ //EnigmailLog.DEBUG("rules.jsm: combineFlagValues(): oldVal=" + oldVal + " newVal=" + newVal + "\n");
+
+ // conflict remains conflict
+ if (oldVal === EnigmailConstants.ENIG_CONFLICT) {
+ return EnigmailConstants.ENIG_CONFLICT;
+ }
+
+ // 'never' and 'always' triggers conflict:
+ if ((oldVal === EnigmailConstants.ENIG_NEVER && newVal === EnigmailConstants.ENIG_ALWAYS) || (oldVal === EnigmailConstants.ENIG_ALWAYS && newVal === EnigmailConstants.ENIG_NEVER)) {
+ return EnigmailConstants.ENIG_CONFLICT;
+ }
+
+ // if there is any 'never' return 'never'
+ // - thus: 'never' and 'maybe' => 'never'
+ if (oldVal === EnigmailConstants.ENIG_NEVER || newVal === EnigmailConstants.ENIG_NEVER) {
+ return EnigmailConstants.ENIG_NEVER;
+ }
+
+ // if there is any 'always' return 'always'
+ // - thus: 'always' and 'maybe' => 'always'
+ if (oldVal === EnigmailConstants.ENIG_ALWAYS || newVal === EnigmailConstants.ENIG_ALWAYS) {
+ return EnigmailConstants.ENIG_ALWAYS;
+ }
+
+ // here, both values are 'maybe', which we return then
+ return EnigmailConstants.ENIG_UNDEF; // maybe
+ }
+};
+
+
+async function deleteAutocryptRule(emailAddr) {
+ const EnigmailAutocrypt = ChromeUtils.import("chrome://openpgp/content/modules/autocrypt.jsm").EnigmailAutocrypt;
+
+ await EnigmailAutocrypt.deleteUser(emailAddr, "1");
+ // make sure that gossip rule is marked as "imported"
+ await EnigmailAutocrypt.setKeyImported(null, emailAddr);
+ // try to apply gossip key
+ await EnigmailAutocrypt.importAutocryptKeys(emailAddr, true);
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/searchCallback.jsm
@@ -0,0 +1,70 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+const EXPORTED_SYMBOLS = ["EnigmailSearchCallback"];
+
+const EnigmailTimer = ChromeUtils.import("chrome://openpgp/content/modules/timer.jsm").EnigmailTimer;
+
+
+var EnigmailSearchCallback = {
+ /**
+ * Set up a callback function on a textbox that tiggers an action.
+ * If ESC is pressed, the input field is emtpied; return triggers the action immediately.
+ *
+ * @param targetObj {XULElement}: the XUL element to observe
+ * @param timoeoutObj {object}: timeoutObj.value will hold the timeout ID
+ * @param actionCallback {function}: callback function that is called if something is typed
+ * @param timeoutMs {number}: delay triggering the function (in miliseconds)
+ */
+ setup: function(targetObj, timeoutObj, actionCallback, timeoutMs = 200) {
+
+
+ function applyActionImmediately() {
+ if (timeoutObj.value) {
+ EnigmailTimer.clearTimeout(timeoutObj.value);
+ timeoutObj.value = null;
+ }
+ applyAction();
+ }
+
+ function applyAction() {
+ actionCallback();
+ }
+
+ timeoutObj.value = null;
+
+
+ targetObj.addEventListener('keypress', function onKeyPress(event) {
+ if (event.type === "keypress") {
+ if (event.keyCode === 27) {
+ // Escape key
+ if (event.target.value !== "") {
+ event.target.value = "";
+ event.preventDefault();
+ }
+ applyActionImmediately();
+
+ return;
+ } else if (event.keyCode === 10 || event.keyCode === 13) {
+ // return key
+ applyActionImmediately();
+ event.preventDefault();
+ return;
+ }
+ }
+
+ if (!timeoutObj.value) {
+ timeoutObj.value = EnigmailTimer.setTimeout(function() {
+ timeoutObj.value = null;
+ applyAction();
+ },
+ timeoutMs);
+ }
+ }, true);
+ }
+};
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/send.jsm
@@ -0,0 +1,146 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailSend"];
+
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
+const EnigmailStdlib = ChromeUtils.import("chrome://openpgp/content/modules/stdlib.jsm").EnigmailStdlib;
+const EnigmailFuncs = ChromeUtils.import("chrome://openpgp/content/modules/funcs.jsm").EnigmailFuncs;
+const Services = ChromeUtils.import("resource://gre/modules/Services.jsm").Services;
+const EnigmailRNG = ChromeUtils.import("chrome://openpgp/content/modules/rng.jsm").EnigmailRNG;
+var MailServices;
+try {
+ MailServices = ChromeUtils.import("resource:///modules/MailServices.jsm").MailServices;
+}
+catch (x){
+ MailServices = ChromeUtils.import("resource:///modules/mailServices.js").MailServices;
+}
+
+var EnigmailSend = {
+ /**
+ * Send out an email
+ *
+ * @param msgData - String: complete MIME string of email (including all headers etc.)
+ * @param compFields - Object: compose fields (nsIMsgCompFields)
+ * @param listener - Object: progress listener (nsIMsgSendListener)
+ *
+ * @return Boolean - true: everything was OK to send the message
+ */
+
+ sendMessage: function(msgData, compFields, listener = null) {
+ EnigmailLog.DEBUG("EnigmailSend.sendMessage()\n");
+ let tmpFile, msgIdentity;
+ try {
+ tmpFile = EnigmailFiles.getTempDirObj();
+ tmpFile.append("message.eml");
+ tmpFile.createUnique(0, 0o600);
+ }
+ catch (ex) {
+ return false;
+ }
+
+ EnigmailFiles.writeFileContents(tmpFile, msgData);
+ EnigmailLog.DEBUG("EnigmailSend.sendMessage: wrote file: " + tmpFile.path + "\n");
+
+ try {
+ msgIdentity = EnigmailStdlib.getIdentityForEmail(compFields.from);
+ }
+ catch (ex) {
+ msgIdentity = EnigmailStdlib.getDefaultIdentity();
+ }
+
+ if (!msgIdentity) {
+ return false;
+ }
+
+ EnigmailLog.DEBUG("EnigmailSend.sendMessage: identity key: " + msgIdentity.identity.key + "\n");
+
+ let acct = EnigmailFuncs.getAccountForIdentity(msgIdentity.identity);
+ if (!acct) return false;
+
+ EnigmailLog.DEBUG("EnigmailSend.sendMessage: account key: " + acct.key + "\n");
+
+ let msgSend = Cc["@mozilla.org/messengercompose/send;1"].createInstance(Ci.nsIMsgSend);
+ msgSend.sendMessageFile(msgIdentity.identity,
+ acct.key,
+ compFields,
+ tmpFile,
+ true, // Delete File On Completion
+ false, (Services.io.offline ? Ci.nsIMsgSend.nsMsgQueueForLater : Ci.nsIMsgSend.nsMsgDeliverNow),
+ null,
+ listener,
+ null,
+ ""); // password
+
+ return true;
+ },
+
+ /**
+ * Send message (simplified API)
+ *
+ * @param aParams: Object -
+ * - identity: Object - The identity the user picked to send the message
+ * - to: String - The recipients. This is a comma-separated list of
+ * valid email addresses that must be escaped already. You probably want to use
+ * nsIMsgHeaderParser.MakeFullAddress to deal with names that contain commas.
+ * - cc (optional) Same remark.
+ * - bcc (optional) Same remark.
+ * - returnReceipt (optional) Boolean: ask for a receipt
+ * - receiptType (optional) Number: default: take from identity
+ * - requestDsn (optional) Boolean: request a Delivery Status Notification
+ * - composeSecure (optional) (contains securityInfo for TB < 64)
+ *
+ * @param body: complete message source
+ * @param callbackFunc: function(Boolean) - return true if message was sent successfully
+ * false otherwise
+ *
+ * @return Boolean - true: everything was OK to send the message
+ */
+ simpleSendMessage: function(aParams, body, callbackFunc) {
+ EnigmailLog.DEBUG("EnigmailSend.simpleSendMessage()\n");
+ let fields = Cc["@mozilla.org/messengercompose/composefields;1"]
+ .createInstance(Ci.nsIMsgCompFields);
+ let identity = aParams.identity;
+
+ fields.from = identity.email;
+ fields.to = aParams.to;
+ if ("cc" in aParams) fields.cc = aParams.cc;
+ if ("bcc" in aParams) fields.bcc = aParams.bcc;
+ fields.returnReceipt = ("returnReceipt" in aParams) ? aParams.returnReceipt : identity.requestReturnReceipt;
+ fields.receiptHeaderType = ("receiptType" in aParams) ? aParams.receiptType : identity.receiptHeaderType;
+ fields.DSN = ("requestDsn" in aParams) ? aParams.requestDsn : identity.requestDSN;
+ if ("composeSecure" in aParams) {
+ if ("securityInfo" in fields) {
+ // TB < 64
+ fields.securityInfo = aParams.securityInfo;
+ }
+ else
+ fields.composeSecure = aParams.composeSecure;
+ }
+
+ fields.messageId = EnigmailRNG.generateRandomString(27) + "-enigmail";
+ body = "Message-Id: " + fields.messageId + "\r\n" + body;
+
+ let listener = {
+ onStartSending: function() {},
+ onProgress: function() {},
+ onStatus: function() {},
+ onGetDraftFolderURI: function() {},
+ onStopSending: function(aMsgID, aStatus, aMsg, aReturnFile) {
+ if (callbackFunc) callbackFunc(true);
+ },
+ onSendNotPerformed: function(aMsgID, aStatus) {
+ if (callbackFunc) callbackFunc(false);
+ }
+ };
+
+ return this.sendMessage(body, fields, listener);
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/singletons.jsm
@@ -0,0 +1,25 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailSingletons"];
+
+
+var EnigmailSingletons = {
+ // handle to most recent message reader window
+ messageReader: null,
+
+ // information about the last PGP/MIME decrypted message (mimeDecrypt)
+ lastDecryptedMessage: {},
+
+ clearLastDecryptedMessage: function() {
+ let lm = this.lastDecryptedMessage;
+ lm.lastMessageData = "";
+ lm.lastMessageURI = null;
+ lm.lastStatus = {};
+ }
+};
+
+EnigmailSingletons.clearLastDecryptedMessage();
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/socks5Proxy.jsm
@@ -0,0 +1,123 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+const EXPORTED_SYMBOLS = ["EnigmailSocks5Proxy"];
+
+const CC = Components.Constructor;
+
+const EnigmailCompat = ChromeUtils.import("chrome://openpgp/content/modules/compat.jsm").EnigmailCompat;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailLazy = ChromeUtils.import("chrome://openpgp/content/modules/lazy.jsm").EnigmailLazy;
+const getEnigmailPrefs = EnigmailLazy.loader("enigmail/prefs.jsm", "EnigmailPrefs");
+
+const CHECK_TOR_URI = "https://check.torproject.org/api/ip";
+const EXPECTED_TOR_EXISTS_RESPONSE = "\"IsTor\":true";
+const TOR_IP_ADDR_PREF = "torIpAddr";
+
+const CONNECTION_FLAGS = 0;
+const SECONDS_TO_WAIT_FOR_CONNECTION = -1;
+
+function createCheckTorURIChannel() {
+ EnigmailLog.DEBUG("socks5proxy.jsm: createCheckTorURIChannel()\n");
+ const ioservice = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+ return ioservice.newChannel2(CHECK_TOR_URI, "UTF-8", null, null, null, null, null, null);
+}
+
+function protocolProxyService() {
+ return Cc["@mozilla.org/network/protocol-proxy-service;1"].getService(Ci.nsIProtocolProxyService);
+}
+
+function createScriptableInputStream(inputStream) {
+ return CC("@mozilla.org/scriptableinputstream;1", "nsIScriptableInputStream", "init")(inputStream);
+}
+
+function buildListener(hasFoundTor, isDoneChecking) {
+ EnigmailLog.DEBUG("socks5proxy.jsm: buildListener()\n");
+ let listener = {
+ onStartRequest: function(request) {},
+ onStopRequest: function(request, statusCode) {
+ isDoneChecking();
+ },
+ QueryInterface: EnigmailCompat.generateQI(["nsIRequestObserver", "nsIStreamListener"])
+ };
+
+ if (EnigmailCompat.isMessageUriInPgpMime()) {
+ // TB >= 67
+ listener.onDataAvailable = function(request, inputStream, offset, count) {
+ const response = createScriptableInputStream(inputStream).read(count);
+ hasFoundTor(response.indexOf(EXPECTED_TOR_EXISTS_RESPONSE) !== -1);
+ };
+ } else {
+ listener.onDataAvailable = function(request, ctxt, inputStream, offset, count) {
+ const response = createScriptableInputStream(inputStream).read(count);
+ hasFoundTor(response.indexOf(EXPECTED_TOR_EXISTS_RESPONSE) !== -1);
+ };
+ }
+
+ return listener;
+}
+
+function getCurrentThread() {
+ return Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager).currentThread;
+}
+
+function filterWith(portPref) {
+ EnigmailLog.DEBUG("socks5proxy.jsm: filterWith()\n");
+
+ const port = getEnigmailPrefs().getPref(portPref);
+ const failoverProxy = null;
+ return {
+ applyFilter: function(proxyService, uri, proxyInfo) {
+ return proxyService.newProxyInfo("socks", getEnigmailPrefs().getPref(TOR_IP_ADDR_PREF), port, CONNECTION_FLAGS, SECONDS_TO_WAIT_FOR_CONNECTION, failoverProxy);
+ },
+ QueryInterface: EnigmailCompat.generateQI(["nsIProtocolProxyFilter"])
+ };
+}
+
+/**
+ * Checks if Tor is running
+ *
+ * @param portPref - string: the preferences key of either torServicePort or torBrowserBundlePort
+ *
+ * @return true if a running Tor service has been found, false otherwise
+ */
+function checkTorExists(portPref) {
+ EnigmailLog.DEBUG("socks5proxy.jsm: checkTorExists()\n");
+ const pps = protocolProxyService().registerFilter(filterWith(portPref), 1);
+
+ let doneCheckingTor = false;
+ let foundTor = false;
+
+ function isDoneChecking() {
+ doneCheckingTor = true;
+ }
+
+ function hasFoundTor(val) {
+ foundTor = val;
+ }
+
+ const listener = buildListener(hasFoundTor, isDoneChecking);
+
+ const sharedContext = null;
+ const ioservice = createCheckTorURIChannel().asyncOpen(listener, sharedContext);
+ const currentThread = getCurrentThread();
+
+ while (!doneCheckingTor) {
+ currentThread.processNextEvent(true);
+ }
+
+ return foundTor;
+}
+
+var EnigmailSocks5Proxy = {
+ checkTorExists: checkTorExists,
+ torIpAddr: function() {
+ EnigmailLog.DEBUG("socks5proxy.jsm: torIpAddr()\n");
+ return getEnigmailPrefs().getPref(TOR_IP_ADDR_PREF);
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/sqliteDb.jsm
@@ -0,0 +1,193 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * Module that provides generic functions for the Enigmail SQLite database
+ */
+
+var EXPORTED_SYMBOLS = ["EnigmailSqliteDb"];
+
+
+
+const Cr = Components.results;
+
+
+const Sqlite = ChromeUtils.import("resource://gre/modules/Sqlite.jsm").Sqlite;
+const EnigmailTimer = ChromeUtils.import("chrome://openpgp/content/modules/timer.jsm").EnigmailTimer;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+
+
+var EnigmailSqliteDb = {
+ /**
+ * Provide an sqlite conection object asynchronously, retrying if needed
+ *
+ * @return {Promise<Sqlite Connection>}: the Sqlite database object
+ */
+
+ openDatabase: function() {
+ EnigmailLog.DEBUG("sqliteDb.jsm: openDatabase()\n");
+ return new Promise((resolve, reject) => {
+ openDatabaseConn(resolve, reject, 100, Date.now() + 10000);
+ });
+ },
+
+ checkDatabaseStructure: async function() {
+ EnigmailLog.DEBUG(`sqliteDb.jsm: checkDatabaseStructure()\n`);
+ let conn;
+ try {
+ conn = await this.openDatabase();
+ await checkAutocryptTable(conn);
+ await checkWkdTable(conn);
+ conn.close();
+ EnigmailLog.DEBUG(`sqliteDb.jsm: checkDatabaseStructure - success\n`);
+ }
+ catch (ex) {
+ EnigmailLog.ERROR(`sqliteDb.jsm: checkDatabaseStructure: ERROR: ${ex}\n`);
+ if (conn) {
+ conn.close();
+ }
+ }
+ }
+};
+
+
+/**
+ * use a promise to open the Enigmail database.
+ *
+ * it's possible that there will be an NS_ERROR_STORAGE_BUSY
+ * so we're willing to retry for a little while.
+ *
+ * @param {function} resolve: function to call when promise succeeds
+ * @param {function} reject: function to call when promise fails
+ * @param {Number} waitms: Integer - number of milliseconds to wait before trying again in case of NS_ERROR_STORAGE_BUSY
+ * @param {Number} maxtime: Integer - unix epoch (in milliseconds) of the point at which we should give up.
+ */
+function openDatabaseConn(resolve, reject, waitms, maxtime) {
+ EnigmailLog.DEBUG("sqliteDb.jsm: openDatabaseConn()\n");
+ Sqlite.openConnection({
+ path: "enigmail.sqlite",
+ sharedMemoryCache: false
+ }).
+ then(connection => {
+ resolve(connection);
+ }).
+ catch(error => {
+ let now = Date.now();
+ if (now > maxtime) {
+ reject(error);
+ return;
+ }
+ EnigmailTimer.setTimeout(function() {
+ openDatabaseConn(resolve, reject, waitms, maxtime);
+ }, waitms);
+ });
+}
+
+
+/**
+ * Ensure that the database structure matches the latest version
+ * (table is available)
+ *
+ * @param connection: Object - SQLite connection
+ *
+ * @return {Promise<Boolean>}
+ */
+async function checkAutocryptTable(connection) {
+ try {
+ let exists = await connection.tableExists("autocrypt_keydata");
+ EnigmailLog.DEBUG("sqliteDB.jsm: checkAutocryptTable - success\n");
+ if (!exists) {
+ await createAutocryptTable(connection);
+ }
+ else {
+ let hasKeyRingInserted = false;
+ await connection.execute("pragma table_info('autocrypt_keydata');", {},
+ function _onRow(row) {
+ let colname = row.getResultByName("name");
+ if (colname === "keyring_inserted") hasKeyRingInserted = true;
+ });
+ if (hasKeyRingInserted) return true;
+
+ await connection.execute("alter table autocrypt_keydata add keyring_inserted text default '0';", {},
+ function _onRow(row) {});
+ let EnigmailAutocrypt = ChromeUtils.import("chrome://openpgp/content/modules/autocrypt.jsm").EnigmailAutocrypt;
+ EnigmailAutocrypt.updateAllImportedKeys();
+ }
+ }
+ catch (error) {
+ EnigmailLog.DEBUG(`sqliteDB.jsm: checkAutocryptTable - error ${error}\n`);
+ throw error;
+ }
+
+ return true;
+}
+/**
+ * Create the "autocrypt_keydata" table and the corresponding index
+ *
+ * @param connection: Object - SQLite connection
+ *
+ * @return {Promise}
+ */
+async function createAutocryptTable(connection) {
+ EnigmailLog.DEBUG("sqliteDB.jsm: createAutocryptTable()\n");
+
+ await connection.execute("create table autocrypt_keydata (" +
+ "email text not null, " + // email address of correspondent
+ "keydata text not null, " + // base64-encoded key as received
+ "fpr text, " + // fingerprint of key
+ "type text not null, " + // key type (1==OpenPGP, regular key. 1g == OpenPGP gossip)
+ "last_seen_autocrypt text, " +
+ "last_seen text not null, " +
+ "state text not null," + // timestamp of last mail received for the email/type combination
+ "keyring_inserted text default '0');"
+ );
+
+ EnigmailLog.DEBUG("sqliteDB.jsm: createAutocryptTable - index\n");
+ await connection.execute("create unique index autocrypt_keydata_i1 on autocrypt_keydata(email, type)");
+
+ return null;
+}
+
+
+
+/**
+ * Ensure that the database has the wkd_lookup_timestamp table.
+ *
+ * @param connection: Object - SQLite connection
+ *
+ * @return Promise
+ */
+async function checkWkdTable(connection) {
+ EnigmailLog.DEBUG("sqliteDB.jsm: checkWkdTable()\n");
+
+ try {
+ let exists = await connection.tableExists("wkd_lookup_timestamp");
+ EnigmailLog.DEBUG("sqliteDB.jsm: checkWkdTable - success\n");
+ if (!exists) {
+ await createWkdTable(connection);
+ }
+ }
+ catch (error) {
+ EnigmailLog.DEBUG("sqliteDB.jsm: checkWkdTable - error\n");
+ throw (error);
+ }
+}
+
+/**
+ * Create the "wkd_lookup_timestamp" table.
+ *
+ * @param connection: Object - SQLite connection
+ *
+ * @return Promise
+ */
+function createWkdTable(connection) {
+ EnigmailLog.DEBUG("sqliteDB.jsm: createWkdTable()\n");
+
+ return connection.execute(
+ "create table wkd_lookup_timestamp (" +
+ "email text not null primary key, " + // email address of correspondent
+ "last_seen integer);"); // timestamp of last mail received for the email/type combination
+}
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/stdlib.jsm
@@ -0,0 +1,131 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * Wrapper library for TB-stdlib to avoid naming conflicts
+ */
+
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailStdlib"];
+
+const {
+ composeInIframe,
+ getEditorForIframe,
+ citeString,
+ htmlToPlainText,
+ simpleWrap,
+ plainTextToHtml,
+ replyAllParams,
+ determineComposeHtml,
+ composeMessageTo,
+ getSignatureContentsForAccount
+} = ChromeUtils.import("chrome://openpgp/content/modules/stdlib/compose.jsm");
+
+const {
+ gIdentities,
+ fillIdentities,
+ getIdentities,
+ getDefaultIdentity,
+ getIdentityForEmail,
+ hasConfiguredAccounts,
+ range,
+ MixIn,
+ combine,
+ entries,
+ dateAsInMessageList,
+ escapeHtml,
+ sanitize,
+ parseMimeLine,
+ encodeUrlParameters,
+ decodeUrlParameters,
+ systemCharset,
+ isOSX,
+ isWindows,
+ isAccel
+} = ChromeUtils.import("chrome://openpgp/content/modules/stdlib/misc.jsm");
+
+const {
+ msgHdrToMessageBody,
+ msgHdrToNeckoURL,
+ msgHdrGetTags,
+ msgUriToMsgHdr,
+ msgHdrGetUri,
+ msgHdrFromNeckoUrl,
+ msgHdrSetTags,
+ msgHdrIsDraft,
+ msgHdrIsSent,
+ msgHdrIsArchive,
+ msgHdrIsInbox,
+ msgHdrIsRss,
+ msgHdrIsNntp,
+ msgHdrIsJunk,
+ msgHdrsMarkAsRead,
+ msgHdrsArchive,
+ msgHdrsDelete,
+ getMail3Pane,
+ msgHdrGetHeaders,
+ msgHdrsModifyRaw
+} = ChromeUtils.import("chrome://openpgp/content/modules/stdlib/msgHdrUtils.jsm");
+
+var EnigmailStdlib = {
+ // compose.jsm
+ 'composeInIframe': composeInIframe,
+ 'getEditorForIframe': getEditorForIframe,
+ 'citeString': citeString,
+ 'htmlToPlainText': htmlToPlainText,
+ 'simpleWrap': simpleWrap,
+ 'plainTextToHtml': plainTextToHtml,
+ 'replyAllParams': replyAllParams,
+ 'determineComposeHtml': determineComposeHtml,
+ 'composeMessageTo': composeMessageTo,
+ 'getSignatureContentsForAccount': getSignatureContentsForAccount,
+
+ // misc.jsm
+ 'gIdentities': gIdentities,
+ 'fillIdentities': fillIdentities,
+ 'getIdentities': getIdentities,
+ 'getDefaultIdentity': getDefaultIdentity,
+ 'getIdentityForEmail': getIdentityForEmail,
+ 'hasConfiguredAccounts': hasConfiguredAccounts,
+ 'range': range,
+ 'MixIn': MixIn,
+ 'combine': combine,
+ 'entries': entries,
+ 'dateAsInMessageList': dateAsInMessageList,
+ 'escapeHtml': escapeHtml,
+ 'sanitize': sanitize,
+ 'parseMimeLine': parseMimeLine,
+ 'encodeUrlParameters': encodeUrlParameters,
+ 'decodeUrlParameters': decodeUrlParameters,
+ 'systemCharset': systemCharset,
+ 'isOSX': isOSX,
+ 'isWindows': isWindows,
+ 'isAccel': isAccel,
+
+ // msgHdrUtils.jsm
+ 'msgHdrToMessageBody': msgHdrToMessageBody,
+ 'msgHdrToNeckoURL': msgHdrToNeckoURL,
+ 'msgHdrGetTags': msgHdrGetTags,
+ 'msgUriToMsgHdr': msgUriToMsgHdr,
+ 'msgHdrGetUri': msgHdrGetUri,
+ 'msgHdrFromNeckoUrl': msgHdrFromNeckoUrl,
+ 'msgHdrSetTags': msgHdrSetTags,
+ 'msgHdrIsDraft': msgHdrIsDraft,
+ 'msgHdrIsSent': msgHdrIsSent,
+ 'msgHdrIsArchive': msgHdrIsArchive,
+ 'msgHdrIsInbox': msgHdrIsInbox,
+ 'msgHdrIsRss': msgHdrIsRss,
+ 'msgHdrIsNntp': msgHdrIsNntp,
+ 'msgHdrIsJunk': msgHdrIsJunk,
+ 'msgHdrsMarkAsRead': msgHdrsMarkAsRead,
+ 'msgHdrsArchive': msgHdrsArchive,
+ 'msgHdrsDelete': msgHdrsDelete,
+ 'getMail3Pane': getMail3Pane,
+ 'msgHdrGetHeaders': msgHdrGetHeaders,
+ 'msgHdrsModifyRaw': msgHdrsModifyRaw
+};
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/stdlib/compose.jsm
@@ -0,0 +1,475 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+/**
+ * @fileoverview Composition-related utils: quoting, wrapping text before
+ * sending a message, converting back and forth between HTML and plain text...
+ * @author Jonathan Protzenko
+ */
+
+var EXPORTED_SYMBOLS = [
+ 'composeInIframe', 'getEditorForIframe',
+ 'quoteMsgHdr', 'citeString',
+ 'htmlToPlainText', 'simpleWrap',
+ 'plainTextToHtml', 'replyAllParams',
+ 'determineComposeHtml', 'composeMessageTo',
+ 'getSignatureContentsForAccount',
+]
+
+const {
+ results: Cr
+} = Components;
+
+const {
+ MsgHdrToMimeMessage,
+ MimeMessage, MimeContainer,
+ MimeBody, MimeUnknown,
+ MimeMessageAttachment
+} = ChromeUtils.import("resource:///modules/gloda/mimemsg.js");
+
+
+var MailServices;
+try {
+ MailServices = ChromeUtils.import("resource:///modules/MailServices.jsm").MailServices;
+}
+catch (x){
+ MailServices = ChromeUtils.import("resource:///modules/mailServices.js").MailServices;
+}
+
+const {
+ gIdentities,
+ fillIdentities,
+ getIdentities,
+ getDefaultIdentity,
+ getIdentityForEmail,
+ hasConfiguredAccounts,
+ range,
+ MixIn,
+ combine,
+ entries,
+ dateAsInMessageList,
+ escapeHtml,
+ sanitize,
+ parseMimeLine,
+ encodeUrlParameters,
+ decodeUrlParameters,
+ systemCharset,
+ isOSX,
+ isWindows,
+ isAccel
+} = ChromeUtils.import("chrome://openpgp/content/modules/stdlib/misc.jsm");
+
+const {
+ msgHdrToMessageBody,
+ msgHdrToNeckoURL,
+ msgHdrGetTags,
+ msgUriToMsgHdr,
+ msgHdrGetUri,
+ msgHdrFromNeckoUrl,
+ msgHdrSetTags,
+ msgHdrIsDraft,
+ msgHdrIsSent,
+ msgHdrIsArchive,
+ msgHdrIsInbox,
+ msgHdrIsRss,
+ msgHdrIsNntp,
+ msgHdrIsJunk,
+ msgHdrsMarkAsRead,
+ msgHdrsArchive,
+ msgHdrsDelete,
+ getMail3Pane,
+ msgHdrGetHeaders,
+ msgHdrsModifyRaw
+} = ChromeUtils.import("chrome://openpgp/content/modules/stdlib/msgHdrUtils.jsm");
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+
+
+function getEditorForIframe(aIframe) {
+ let w = aIframe.contentWindow;
+ let s = w.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIEditingSession);
+ return s.getEditorForWindow(w);
+}
+
+function composeInIframe(aIframe, {
+ msgHdr,
+ compType,
+ identity,
+}) {
+ let fields = Cc["@mozilla.org/messengercompose/composefields;1"]
+ .createInstance(Ci.nsIMsgCompFields);
+ let params = Cc["@mozilla.org/messengercompose/composeparams;1"]
+ .createInstance(Ci.nsIMsgComposeParams);
+ params.identity = identity;
+ if (msgHdr) {
+ params.origMsgHdr = msgHdr;
+ params.originalMsgURI = msgHdrGetUri(msgHdr);
+ }
+ params.composeFields = fields;
+ params.type = compType;
+
+ let compose = MailServices.compose.initCompose(params, getMail3Pane(), aIframe.docShell);
+ EnigmailLog.DEBUG("editor= " + getEditorForIframe(aIframe).toString() + ", iframe= " + aIframe.toString() + "\n");
+ compose.initEditor(getEditorForIframe(aIframe), aIframe.contentWindow);
+}
+
+/**
+ * A function that properly quotes a plaintext email.
+ * @param {String} aStr The mail body that we're expected to quote.
+ * @return {String} The quoted mail body with >'s properly taken care of.
+ */
+function citeString(aStr) {
+ let l = aStr.length;
+ return aStr.replace("\n", function(match, offset, str) {
+ // http://mxr.mozilla.org/comm-central/source/mozilla/editor/libeditor/text/nsInternetCiter.cpp#96
+ if (offset < l - 1) {
+ if (str[offset + 1] != ">" && str[offset + 1] != "\n" && str[offset + 1] != "\r")
+ return "\n> ";
+ else
+ return "\n>";
+ }
+ else {
+ return match;
+ }
+ }, "g");
+}
+
+/**
+ * Wrap some text. Beware, that function doesn't do rewrapping, and only
+ * operates on non-quoted lines. This is only useful in our very specific case
+ * where the quoted lines have been properly wrapped for format=flowed already,
+ * and the non-quoted lines are the only ones that need wrapping for
+ * format=flowed.
+ * Beware, this function will treat all lines starting with >'s as quotations,
+ * even user-inserted ones. We would need support from the editor to proceed
+ * otherwise, and the current textarea doesn't provide this.
+ * This function, when breaking lines, will do space-stuffing per the RFC if
+ * after the break the text starts with From or >.
+ * @param {String} txt The text that should be wrapped.
+ * @param {Number} width (optional) The width we should wrap to. Default to 72.
+ * @return {String} The text with non-quoted lines wrapped. This is suitable for
+ * sending as format=flowed.
+ */
+function simpleWrap(txt, width) {
+ if (!width)
+ width = 72;
+
+ function maybeEscape(line) {
+ if (line.indexOf("From") === 0 || line.indexOf(">") === 0)
+ return (" " + line);
+ else
+ return line;
+ }
+
+ /**
+ * That function takes a (long) line, and splits it into many lines.
+ * @param soFar {Array String} an accumulator of the lines we've wrapped already
+ * @param remaining {String} the remaining string to wrap
+ */
+ function splitLongLine(soFar, remaining) {
+ if (remaining.length > width) {
+ // Start at the end of the line, and move back until we find a word
+ // boundary.
+ let i = width - 1;
+ while (remaining[i] != " " && i > 0)
+ i--;
+ // We found a word boundary, break there
+ if (i > 0) {
+ // This includes the trailing space that indicates that we are wrapping
+ // a long line with format=flowed.
+ soFar.push(maybeEscape(remaining.substring(0, i + 1)));
+ return splitLongLine(soFar, remaining.substring(i + 1, remaining.length));
+ }
+ else {
+ // No word boundary, break at the first space
+ let j = remaining.indexOf(" ");
+ if (j > 0) {
+ // Same remark about the trailing space.
+ soFar.push(maybeEscape(remaining.substring(0, j + 1)));
+ return splitLongLine(soFar, remaining.substring(j + 1, remaining.length));
+ }
+ else {
+ // Make sure no one interprets this as a line continuation.
+ soFar.push(remaining.trimRight());
+ return soFar.join("\n");
+ }
+ }
+ }
+ else {
+ // Same remark about the trailing space.
+ soFar.push(maybeEscape(remaining.trimRight()));
+ return soFar.join("\n");
+ }
+ }
+
+ let lines = txt.split(/\r?\n/);
+
+ lines.forEach(function(line, i) {
+ if (line.length > width && line[0] != ">")
+ lines[i] = splitLongLine([], line);
+ });
+ return lines.join("\n");
+}
+
+/**
+ * Convert HTML into text/plain suitable for insertion right away in the mail
+ * body. If there is text with >'s at the beginning of lines, these will be
+ * space-stuffed, and the same goes for Froms. <blockquote>s will be converted
+ * with the suitable >'s at the beginning of the line, and so on...
+ * This function also takes care of rewrapping at 72 characters, so your quoted
+ * lines will be properly wrapped too. This means that you can add some text of
+ * your own, and then pass this to simpleWrap, it should "just work" (unless
+ * the user has edited a quoted line and made it longer than 990 characters, of
+ * course).
+ * @param {String} aHtml A string containing the HTML that's to be converted.
+ * @return {String} A text/plain string suitable for insertion in a mail body.
+ */
+function htmlToPlainText(aHtml) {
+ // Yes, this is ridiculous, we're instanciating composition fields just so
+ // that they call ConvertBufPlainText for us. But ConvertBufToPlainText
+ // really isn't easily scriptable, so...
+ let fields = Cc["@mozilla.org/messengercompose/composefields;1"]
+ .createInstance(Ci.nsIMsgCompFields);
+ fields.body = aHtml;
+ fields.forcePlainText = true;
+ fields.ConvertBodyToPlainText();
+ return fields.body;
+}
+
+/**
+ * @ignore
+ */
+function citeLevel(line) {
+ let i;
+ for (i = 0; line[i] == ">" && i < line.length; ++i)
+ ; // nop
+ return i;
+};
+
+/**
+ * Just try to convert quoted lines back to HTML markup (<blockquote>s).
+ * @param {String} txt
+ * @return {String}
+ */
+function plainTextToHtml(txt) {
+ let lines = txt.split(/\r?\n/);
+ let newLines = [];
+ let level = 0;
+ for (let line of lines) {
+ let newLevel = citeLevel(line);
+ if (newLevel > level)
+ for (let i = level; i < newLevel; ++i)
+ newLines.push('<blockquote type="cite">');
+ if (newLevel < level)
+ for (let i = newLevel; i < level; ++i)
+ newLines.push('</blockquote>');
+ let newLine = line[newLevel] == " " ? escapeHtml(line.substring(newLevel + 1, line.length)) : escapeHtml(line.substring(newLevel, line.length));
+ newLines.push(newLine);
+ level = newLevel;
+ }
+ return newLines.join("\n");
+}
+
+function parse(aMimeLine) {
+ if (!aMimeLine)
+ return [
+ [],
+ []
+ ];
+ let emails = {};
+ let fullNames = {};
+ let names = {};
+ let numAddresses = MailServices.headerParser.parseHeadersWithArray(aMimeLine, emails, names, fullNames);
+ return [names.value, emails.value];
+}
+
+/**
+ * Analyze a message header, and then return all the compose parameters for the
+ * reply-all case.
+ * @param {nsIIdentity} The identity you've picked for the reply.
+ * @param {nsIMsgDbHdr} The message header.
+ * @param {k} The function to call once we've determined all parameters. Take an
+ * argument like
+ * {{ to: [[name, email]], cc: [[name, email]], bcc: [[name, email]]}}
+ */
+function replyAllParams(aIdentity, aMsgHdr, k) {
+ // Do the whole shebang to find out who to send to...
+ let [
+ [author],
+ [authorEmailAddress]
+ ] = parse(aMsgHdr.author);
+ let [recipients, recipientsEmailAddresses] = parse(aMsgHdr.recipients);
+ let [ccList, ccListEmailAddresses] = parse(aMsgHdr.ccList);
+ let [bccList, bccListEmailAddresses] = parse(aMsgHdr.bccList);
+ authorEmailAddress = authorEmailAddress.toLowerCase();
+ recipientsEmailAddresses = recipientsEmailAddresses.map(x => x.toLowerCase());
+ ccListEmailAddresses = ccListEmailAddresses.map(x => x.toLowerCase());
+ bccListEmailAddresses = bccListEmailAddresses.map(x => x.toLowerCase());
+ let identity = aIdentity;
+ let identityEmail = identity.email.toLowerCase();
+ let to = [],
+ cc = [],
+ bcc = [];
+
+ let isReplyToOwnMsg = false;
+ for (let currentIdentity of getIdentities()) {
+ let email = currentIdentity.identity.email.toLowerCase();
+ if (email == authorEmailAddress)
+ isReplyToOwnMsg = true;
+ if (recipientsEmailAddresses.some(x => x == email))
+ isReplyToOwnMsg = false;
+ if (ccListEmailAddresses.some(x => x == email))
+ isReplyToOwnMsg = false;
+ }
+
+ // Actually we are implementing the "Reply all" logic... that's better, no one
+ // wants to really use reply anyway
+ if (isReplyToOwnMsg) {
+ to = recipients.map((r, i) => [r, recipientsEmailAddresses[i]]);
+ }
+ else {
+ to = [
+ [author, authorEmailAddress]
+ ];
+ }
+ cc = ccList.map((cc, i) => [cc, ccListEmailAddresses[i]]).
+ filter((e, i) => e[1] != identityEmail);
+ if (!isReplyToOwnMsg) {
+ cc = cc.concat(recipients.map((r, i) => [r, recipientsEmailAddresses[i]]).filter((e, i) => e[1] != identityEmail));
+ }
+ bcc = bccList.map((bcc, i) => [bcc, bccListEmailAddresses]);
+
+ let finish = function(to, cc, bcc) {
+ let hashMap = {};
+ for (let [name, email] of to)
+ hashMap[email] = null;
+ cc = cc.filter(function([name, email]) {
+ let r = (email in hashMap);
+ hashMap[email] = null;
+ return !r
+ });
+ bcc = bcc.filter(function([name, email]) {
+ let r = (email in hashMap);
+ hashMap[email] = null;
+ return !r
+ });
+ k({
+ to: to,
+ cc: cc,
+ bcc: bcc
+ });
+ }
+
+ // Do we have a Reply-To header?
+ msgHdrGetHeaders(aMsgHdr, function(aHeaders) {
+ if (aHeaders.has("reply-to")) {
+ let [names, emails] = parse(aHeaders.get("reply-to"));
+ emails = emails.map(email => email.toLowerCase());
+ if (emails.length) {
+ // Invariant: at this stage, we only have one item in to.
+ cc = cc.concat([to[0]]); // move the to in cc
+ to = combine(names, emails);
+ }
+ }
+ finish(to, cc, bcc);
+ });
+}
+
+/**
+ * This function replaces nsMsgComposeService::determineComposeHTML, which is
+ * marked as [noscript], just to make our lives complicated. [insert random rant
+ * here].
+ *
+ * @param aIdentity (optional) You can specify the identity which you would like
+ * to get the preference for.
+ * @return a bool which is true if you should compose in HTML
+ */
+function determineComposeHtml(aIdentity) {
+ if (!aIdentity)
+ aIdentity = getDefaultIdentity().identity;
+
+ if (aIdentity) {
+ return (aIdentity.composeHtml == Ci.nsIMsgCompFormat.HTML);
+ }
+ else {
+ return Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefService)
+ .getBranch(null)
+ .getBoolPref("mail.compose_html");
+ }
+}
+
+/**
+ * Open a composition window for the given email address.
+ * @param aEmail {String}
+ * @param aDisplayedFolder {nsIMsgFolder} pass gFolderDisplay.displayedFolder
+ */
+function composeMessageTo(aEmail, aDisplayedFolder) {
+ let fields = Cc["@mozilla.org/messengercompose/composefields;1"]
+ .createInstance(Ci.nsIMsgCompFields);
+ let params = Cc["@mozilla.org/messengercompose/composeparams;1"]
+ .createInstance(Ci.nsIMsgComposeParams);
+ fields.to = aEmail
+ params.type = Ci.nsIMsgCompType.New;
+ params.format = Ci.nsIMsgCompFormat.Default;
+ if (aDisplayedFolder) {
+ params.identity = MailServices.accounts
+ .getFirstIdentityForServer(aDisplayedFolder.server);
+ }
+ params.composeFields = fields;
+ MailServices.compose.OpenComposeWindowWithParams(null, params);
+}
+
+/**
+ * Returns signature contents depending on account settings of the identity.
+ * HTML signature is converted to plain text.
+ * @param {nsIIdentity} The identity you've picked for the reply.
+ * @return {String} plain text signature
+ */
+function getSignatureContentsForAccount(aIdentity) {
+ let signature = "";
+ if (!aIdentity)
+ return signature;
+
+ if (aIdentity.attachSignature && aIdentity.signature) {
+ let charset = systemCharset();
+ const replacementChar =
+ Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER;
+ let fstream = Cc["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Ci.nsIFileInputStream);
+ let cstream = Cc["@mozilla.org/intl/converter-input-stream;1"]
+ .createInstance(Ci.nsIConverterInputStream);
+ try {
+ fstream.init(aIdentity.signature, -1, 0, 0);
+ try {
+ cstream.init(fstream, charset, 1024, replacementChar);
+ }
+ catch (e) {
+ EnigmailLog.ERROR("ConverterInputStream init error: " + e +
+ "\n charset: " + charset + "\n");
+ cstream.init(fstream, "UTF-8", 1024, replacementChar);
+ }
+ let str = {};
+ while (cstream.readString(4096, str) != 0) {
+ signature += str.value;
+ }
+ if (aIdentity.signature.path.match(/\.html?$/)) {
+ signature = htmlToPlainText(signature);
+ }
+ }
+ catch (e) {
+ EnigmailLog.ERROR("Signature file stream error: " + e + "\n");
+ }
+ cstream.close();
+ fstream.close();
+ // required for stripSignatureIfNeeded working properly
+ signature = signature.replace(/\r?\n/g, "\n");
+ }
+ else {
+ signature = aIdentity.htmlSigFormat ? htmlToPlainText(aIdentity.htmlSigText) : aIdentity.htmlSigText;
+ }
+ return signature;
+}
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/stdlib/misc.jsm
@@ -0,0 +1,395 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+/**
+ * @fileoverview This file provides various utilities: some helpers to deal with
+ * identity management, some helpers for JS programming, some helpers for
+ * low-level XPCOM stuff...
+ */
+
+var EXPORTED_SYMBOLS = [
+ // Identity management helpers
+ 'gIdentities', 'fillIdentities', 'getIdentities', 'getDefaultIdentity', 'getIdentityForEmail',
+ // Account management helpers:
+ 'hasConfiguredAccounts',
+ // JS programming helpers
+ 'range', 'MixIn', 'combine', 'entries',
+ // XPCOM helpers
+ 'NS_FAILED', 'NS_SUCCEEDED',
+ // Various formatting helpers
+ 'dateAsInMessageList', 'escapeHtml', 'sanitize', 'parseMimeLine',
+ // Useful for web content
+ 'encodeUrlParameters', 'decodeUrlParameters',
+ // Character set helpers
+ 'systemCharset',
+ // Platform-specific idioms
+ 'isOSX', 'isWindows', 'isAccel'
+]
+
+const fixIterator = ChromeUtils.import("resource:///modules/iteratorUtils.jsm").fixIterator;
+const XPCOMUtils = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm").XPCOMUtils;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+var MailServices;
+try {
+ MailServices = ChromeUtils.import("resource:///modules/MailServices.jsm").MailServices;
+}
+catch (x){
+ MailServices = ChromeUtils.import("resource:///modules/mailServices.js").MailServices;
+}
+
+XPCOMUtils.defineLazyServiceGetter(MailServices, "i18nDateFormatter",
+ "@mozilla.org/intl/scriptabledateformat;1");
+
+
+let isOSX = ("nsILocalFileMac" in Ci);
+let isWindows = ("@mozilla.org/windows-registry-key;1" in Cc);
+
+function isAccel(event) {
+ return isOSX && event.metaKey || event.ctrlKey;
+}
+
+/**
+ * Low-level XPCOM-style macro. You might need this for the composition and
+ * sending listeners which will pass you some status codes.
+ * @param {Int} v The status code
+ * @return {Bool}
+ */
+function NS_FAILED(v) {
+ return (v & 0x80000000);
+}
+
+/**
+ * Low-level XPCOM-style macro. You might need this for the composition and
+ * sending listeners which will pass you some status codes.
+ * @param {Int} v The status code
+ * @return {Bool}
+ */
+function NS_SUCCEEDED(v) {
+ return !NS_FAILED(v);
+}
+
+/**
+ * Python-style range function to use in list comprehensions.
+ * @param {Number} begin
+ * @param {Number} end
+ * @return {Generator} An iterator that yields from begin to end - 1.
+ */
+function* range(begin, end) {
+ for (let i = begin; i < end; ++i) {
+ yield i;
+ }
+}
+
+/**
+ * Helper function to simplify iteration over key/value store objects.
+ * From https://esdiscuss.org/topic/es6-iteration-over-object-values
+ * @param {Object} anObject
+ */
+function* entries(anObject) {
+ for (let key of Object.keys(anObject)) {
+ yield [key, anObject[key]];
+ }
+}
+
+/**
+ * MixIn-style helper. Adds aMixIn properties, getters and setters to
+ * aConstructor.
+ * @param {Object} aConstructor
+ * @param {Object} aMixIn
+ */
+function MixIn(aConstructor, aMixIn) {
+ let proto = aConstructor.prototype;
+ for (let [name, func] of entries(aMixIn)) {
+ if (name.substring(0, 4) == "get_")
+ proto.__defineGetter__(name.substring(4), func);
+ else
+ proto[name] = func;
+ }
+}
+
+/**
+ * A global pointer to all the identities known for the user. Feel free to call
+ * fillIdentities again if you feel that the user has updated them!
+ * The keys are email addresses, the values are <tt>nsIMsgIdentity</tt> objects.
+ *
+ * @const
+ */
+let gIdentities = {};
+
+/**
+ * This function you should call to populate the gIdentities global object. The
+ * recommended time to call this is after the mail-startup-done event, although
+ * doing this at overlay load-time seems to be fine as well.
+ * There is a "default" key, that we guarantee to be non-null, by picking the
+ * first account's first valid identity if the default account doesn't have any
+ * valid identity associated.
+ * @param aSkipNntp (optional) Should we avoid including nntp identities in the
+ * list?
+ * @deprecated Use getIdenties() instead
+ */
+function fillIdentities(aSkipNntp) {
+ EnigmailLog.WARNING("fillIdentities is deprecated! Use getIdentities instead!\n");
+ EnigmailLog.DEBUG("Filling identities with skipnntp = " + aSkipNntp.toString() + "\n");
+
+ for (let currentIdentity of getIdentities(aSkipNntp)) {
+ gIdentities[currentIdentity.identity.email] = currentIdentity.identity;
+ if (currentIdentity.isDefault) {
+ gIdentities["default"] = currentIdentity.identity;
+ }
+ }
+
+ if (!("default" in gIdentities)) {
+ gIdentities["default"] = getIdentities()[0].identity;
+ }
+}
+
+/**
+ * Returns the default identity in the form { boolean isDefault; nsIMsgIdentity identity }
+ */
+function getDefaultIdentity() {
+ return getIdentities().find(x => x.isDefault);
+}
+
+/**
+ * Returns a list of all identities in the form [{ boolean isDefault; nsIMsgIdentity identity }].
+ * It is assured that there is exactly one default identity.
+ * If only the default identity is needed, getDefaultIdentity() can be used.
+ * @param aSkipNntpIdentities (default: true) Should we avoid including nntp identities in the list?
+ */
+function getIdentities(aSkipNntpIdentities = true) {
+ let identities = [];
+ for (let account of fixIterator(MailServices.accounts.accounts, Ci.nsIMsgAccount)) {
+ let server = account.incomingServer;
+ if (aSkipNntpIdentities && (!server || server.type != "pop3" && server.type != "imap")) {
+ continue;
+ }
+ for (let currentIdentity of fixIterator(account.identities, Ci.nsIMsgIdentity)) {
+ // We're only interested in identities that have a real email.
+ if (currentIdentity.email) {
+ identities.push({
+ isDefault: (currentIdentity == MailServices.accounts.defaultAccount ? MailServices.accounts.defaultAccount.defaultIdentity : false),
+ identity: currentIdentity
+ });
+ }
+ }
+ }
+ if (identities.length == 0) {
+ EnigmailLog.DEBUG("Didn't find any identities!\n");
+ }
+ else {
+ if (!identities.some(x => x.isDefault)) {
+ EnigmailLog.DEBUG("Didn't find any default key - mark the first identity as default!\n");
+ identities[0].isDefault = true;
+ }
+ }
+ return identities;
+}
+
+/*
+ * Searches a given email address in all identities and returns the corresponding identity.
+ * @param {String} anEmailAddress Email address to be searched in the identities
+ * @returns {{Boolean} isDefault, {{nsIMsgIdentity} identity} if found, otherwise undefined
+ */
+function getIdentityForEmail(anEmailAddress) {
+ return getIdentities(false).find(ident => ident.identity.email.toLowerCase() == anEmailAddress.toLowerCase());
+}
+
+/**
+ * A stupid formatting function that uses the i18nDateFormatter XPCOM component
+ * to format a date just like in the message list
+ * @param {Date} aDate a javascript Date object
+ * @return {String} a string containing the formatted date
+ */
+function dateAsInMessageList(aDate) {
+ const DATE_2DIGIT = "2-digit";
+ const DATE_4DIGIT = "numeric";
+ let now = new Date();
+ // Is it today?
+ let isToday =
+ now.getFullYear() == aDate.getFullYear() &&
+ now.getMonth() == aDate.getMonth() &&
+ now.getDate() == aDate.getDate();
+
+ let options = {
+ hour: DATE_2DIGIT,
+ minute: DATE_2DIGIT
+ };
+
+ if (!isToday) {
+ options.day = DATE_2DIGIT;
+ options.month = DATE_2DIGIT;
+ let year = aDate.getFullYear();
+ if (year > 2099) {
+ options.year = DATE_4DIGIT;
+ }
+ else {
+ options.year = DATE_2DIGIT;
+ }
+ }
+
+ return new Intl.DateTimeFormat([], options).format(aDate);
+}
+
+
+const RE_SANITIZE = /[\u0000-\u0008\u000b-\u000c\u000e-\u001f]/g;
+
+/**
+ * Helper function to remove non-printable characters from a string -- injecting
+ * these in an XML or XHTML document would cause an error.
+ * @param {String} s input text
+ * @param {String} The sanitized string.
+ */
+function sanitize(s) {
+ return (s || "").replace(RE_SANITIZE, "");
+}
+
+/**
+ * Helper function to escape some XML chars, so they display properly in
+ * innerHTML.
+ * @param {String} s input text
+ * @return {String} The string with <, >, and & replaced by the corresponding entities.
+ */
+function escapeHtml(s) {
+ s += "";
+ // stolen from selectionsummaries.js (thanks davida!)
+ return sanitize(s.replace(/[<>&]/g, function(s) {
+ switch (s) {
+ case "<":
+ return "<";
+ case ">":
+ return ">";
+ case "&":
+ return "&";
+ default:
+ throw Error("Unexpected match");
+ }
+ }));
+}
+
+/**
+ * Wraps the low-level header parser stuff.
+ * @param {String} aMimeLine a line that looks like "John <john@cheese.com>, Jane <jane@wine.com>"
+ * @param {Boolean} aDontFix (optional) Default to false. Shall we return an
+ * empty array in case aMimeLine is empty?
+ * @return {Array} a list of { email, name } objects
+ */
+function parseMimeLine(aMimeLine, aDontFix) {
+ if (aMimeLine == null) {
+ EnigmailLog.DEBUG("Empty aMimeLine?!!\n");
+ return [];
+ }
+ let emails = {};
+ let fullNames = {};
+ let names = {};
+ let numAddresses = MailServices.headerParser.parseHeadersWithArray(aMimeLine,
+ emails,
+ names,
+ fullNames);
+ if (numAddresses)
+ return [...range(0, numAddresses)].map(i => {
+ return {
+ email: emails.value[i],
+ name: names.value[i],
+ fullName: fullNames.value[i]
+ };
+ });
+ else if (aDontFix)
+ return [];
+ else
+ return [{
+ email: "",
+ name: "-",
+ fullName: "-"
+ }];
+}
+
+/**
+ * Takes an object whose keys are the parameter names, whose values are strings
+ * that are to be encoded in the url.
+ * @param aObj
+ * @return param1=val1¶m2=val2 etc.
+ */
+function encodeUrlParameters(aObj) {
+ let kv = [];
+ for (let [k, v] of entries(aObj)) {
+ kv.push(k + "=" + encodeURIComponent(v));
+ }
+ return kv.join("&");
+}
+
+/**
+ * Takes the <b>entire</b> query string and returns an object whose keys are the
+ * parameter names and values are corresponding values.
+ * @param aStr The entire query string
+ * @return An object that holds the decoded data
+ */
+function decodeUrlParameters(aStr) {
+ let params = {};
+ let i = aStr.indexOf("?");
+ if (i >= 0) {
+ let query = aStr.substring(i + 1, aStr.length);
+ let keyVals = query.split("&");
+ for (let keyVal of keyVals) {
+ let [key, val] = keyVal.split("=");
+ val = decodeURIComponent(val);
+ params[key] = val;
+ }
+ }
+ return params;
+}
+
+/**
+ * Returns a system character set string, which is system code page on Windows,
+ * LANG environment variable's encoding on Unix-like OS, otherwise UTF-8.
+ * @return {String} a character set string
+ */
+function systemCharset() {
+ let charset = "UTF-8";
+ if ("@mozilla.org/windows-registry-key;1" in Cc) {
+ let registry = Cc["@mozilla.org/windows-registry-key;1"]
+ .createInstance(Ci.nsIWindowsRegKey);
+ registry.open(registry.ROOT_KEY_LOCAL_MACHINE,
+ "SYSTEM\\CurrentControlSet\\Control\\Nls\\CodePage",
+ registry.ACCESS_READ);
+ let codePage = registry.readStringValue("ACP");
+ if (codePage) {
+ charset = "CP" + codePage;
+ }
+ registry.close();
+ }
+ else {
+ let env = Cc["@mozilla.org/process/environment;1"]
+ .getService(Ci.nsIEnvironment);
+ let lang = env.get("LANG").split(".");
+ if (lang.length > 1) {
+ charset = lang[1];
+ }
+ }
+ return charset;
+}
+
+function combine(a1, a2) {
+ if (a1.length != a2.length)
+ throw new Error("combine: the given arrays have different lengths");
+ return [...range(0, a1.length)].map(i => [a1[i], a2[i]]);
+}
+
+
+/**
+ * Determine if at least one account / identity is configured
+ * @return {Bool}
+ */
+function hasConfiguredAccounts() {
+ let accountManager = Cc["@mozilla.org/messenger/account-manager;1"].getService(Ci.nsIMsgAccountManager);
+
+ for (let acct = 0; acct < accountManager.accounts.length; acct++) {
+ let ac = accountManager.accounts.queryElementAt(acct, Ci.nsIMsgAccount);
+
+ if (ac.incomingServer.type !== "none") {
+ if (ac.defaultIdentity.email.length > 0) return true;
+ }
+ }
+
+ return false;
+}
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/stdlib/msgHdrUtils.jsm
@@ -0,0 +1,535 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+/**
+ * @fileoverview A whole bunch of utility functions that will abstract away
+ * various low-level nsIMsgDbHdr operations. The idea is to save time by not
+ * having to lookup how to do simple actions.
+ * @author Jonathan Protzenko
+ */
+
+var EXPORTED_SYMBOLS = [
+ // Low-level XPCOM boring stuff
+ 'msgHdrToMessageBody', 'msgHdrToNeckoURL', 'msgHdrGetTags', 'msgUriToMsgHdr',
+ 'msgHdrGetUri', 'msgHdrFromNeckoUrl', 'msgHdrSetTags',
+ // Quickly identify a message
+ 'msgHdrIsDraft', 'msgHdrIsSent', 'msgHdrIsArchive', 'msgHdrIsInbox',
+ 'msgHdrIsRss', 'msgHdrIsNntp', 'msgHdrIsJunk',
+ // Actions on a set of message headers
+ 'msgHdrsMarkAsRead', 'msgHdrsArchive', 'msgHdrsDelete',
+ // Doesn't really belong here
+ 'getMail3Pane',
+ // Higher-level functions
+ 'msgHdrGetHeaders',
+ // Modify messages, raw.
+ 'msgHdrsModifyRaw',
+]
+
+const Cr = Components.results;
+
+// from mailnews/base/public/nsMsgFolderFlags.idl
+const nsMsgFolderFlags_SentMail = 0x00000200;
+const nsMsgFolderFlags_Drafts = 0x00000400;
+const nsMsgFolderFlags_Archive = 0x00004000;
+const nsMsgFolderFlags_Inbox = 0x00001000;
+
+const PR_WRONLY = 0x02;
+
+const XPCOMUtils = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm").XPCOMUtils;
+const {
+ MsgHdrToMimeMessage,
+ MimeMessage, MimeContainer,
+ MimeBody, MimeUnknown,
+ MimeMessageAttachment
+} = ChromeUtils.import("resource:///modules/gloda/mimemsg.js");
+const GlodaUtils = ChromeUtils.import("resource:///modules/gloda/utils.js").GlodaUtils;
+const {
+ fixIterator, toXPCOMArray, toArray
+} =ChromeUtils.import("resource:///modules/iteratorUtils.jsm");
+const Services = ChromeUtils.import("resource://gre/modules/Services.jsm").Services;
+const EnigmailCompat = ChromeUtils.import("chrome://openpgp/content/modules/compat.jsm").EnigmailCompat;
+
+var MailServices;
+try {
+ MailServices = ChromeUtils.import("resource:///modules/MailServices.jsm").MailServices;
+}
+catch (x){
+ MailServices = ChromeUtils.import("resource:///modules/mailServices.js").MailServices;
+}
+
+const {
+ gIdentities,
+ fillIdentities,
+ getIdentities,
+ getDefaultIdentity,
+ getIdentityForEmail,
+ hasConfiguredAccounts,
+ range,
+ MixIn,
+ combine,
+ entries,
+ dateAsInMessageList,
+ escapeHtml,
+ sanitize,
+ parseMimeLine,
+ encodeUrlParameters,
+ decodeUrlParameters,
+ systemCharset,
+ isOSX,
+ isWindows,
+ isAccel
+} = ChromeUtils.import("chrome://openpgp/content/modules/stdlib/misc.jsm");
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+
+// Adding a messenger lazy getter to the MailServices even though it's not a service
+XPCOMUtils.defineLazyGetter(MailServices, "messenger", function() {
+ return Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
+});
+
+/**
+ * Get a given message header's uri.
+ * @param {nsIMsgDbHdr} aMsg The message
+ * @return {String}
+ */
+function msgHdrGetUri(aMsg) {
+ return aMsg.folder.getUriForMsg(aMsg);
+}
+
+/**
+ * Get a msgHdr from a message URI (msgHdr.URI).
+ * @param {String} aUri The URI of the message
+ * @return {nsIMsgDbHdr}
+ */
+function msgUriToMsgHdr(aUri) {
+ try {
+ let messageService = MailServices.messenger.messageServiceFromURI(aUri);
+ return messageService.messageURIToMsgHdr(aUri);
+ }
+ catch (e) {
+ dump("Unable to get " + aUri + " — returning null instead");
+ return null;
+ }
+}
+
+/**
+ * Tells if the message is in the account's inbox
+ * @param {nsIMsgDbHdr} msgHdr The message header to examine
+ * @return {bool}
+ */
+function msgHdrIsInbox(msgHdr) {
+ return msgHdr.folder.getFlag(nsMsgFolderFlags_Inbox);
+}
+
+/**
+ * Tells if the message is a draft message
+ * @param {nsIMsgDbHdr} msgHdr The message header to examine
+ * @return {bool}
+ */
+function msgHdrIsDraft(msgHdr) {
+ return msgHdr.folder.getFlag(nsMsgFolderFlags_Drafts);
+}
+
+/**
+ * Tells if the message is a sent message
+ * @param {nsIMsgDbHdr} msgHdr The message header to examine
+ * @return {bool}
+ */
+function msgHdrIsSent(msgHdr) {
+ return msgHdr.folder.getFlag(nsMsgFolderFlags_SentMail);
+}
+
+/**
+ * Tells if the message is an archived message
+ * @param {nsIMsgDbHdr} msgHdr The message header to examine
+ * @return {bool}
+ */
+function msgHdrIsArchive(msgHdr) {
+ return msgHdr.folder.getFlag(nsMsgFolderFlags_Archive);
+}
+
+/**
+ * Get a nsIMsgDbHdr from a Necko URL.
+ * @param {String} The URL
+ * @return {nsIMsgDbHdr} The message header.
+ */
+function msgHdrFromNeckoUrl(aUrl) {
+ return aUrl.QueryInterface(Ci.nsIMsgMessageUrl).messageHeader;
+}
+
+/**
+ * Get a string containing the body of a messsage.
+ * @param {nsIMsgDbHdr} aMessageHeader The message header
+ * @param {bool} aStripHtml Keep html?
+ * @return {string}
+ */
+function msgHdrToMessageBody(aMessageHeader, aStripHtml, aLength) {
+ let messenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
+ let listener = Cc["@mozilla.org/network/sync-stream-listener;1"].createInstance(Ci.nsISyncStreamListener);
+ let uri = aMessageHeader.folder.getUriForMsg(aMessageHeader);
+ messenger.messageServiceFromURI(uri).streamMessage(uri, listener, null, null, false, "");
+ let folder = aMessageHeader.folder;
+ /*
+ * AUTF8String getMsgTextFromStream(in nsIInputStream aStream, in ACString aCharset,
+ in unsigned long aBytesToRead, in unsigned long aMaxOutputLen,
+ in boolean aCompressQuotes, in boolean aStripHTMLTags,
+ out ACString aContentType);
+ */
+ return folder.getMsgTextFromStream(
+ listener.inputStream, aMessageHeader.Charset, 2 * aLength, aLength, false, aStripHtml, {});
+}
+
+/**
+ * Get a nsIURI from a nsIMsgDBHdr
+ * @param {nsIMsgDbHdr} aMsgHdr The message header
+ * @return {nsIURI}
+ */
+function msgHdrToNeckoURL(aMsgHdr) {
+ let uri = aMsgHdr.folder.getUriForMsg(aMsgHdr);
+ let neckoURL = {};
+ let msgService = MailServices.messenger.messageServiceFromURI(uri);
+ msgService.GetUrlForUri(uri, neckoURL, null);
+ return neckoURL.value;
+}
+
+/**
+ * Given a msgHdr, return a list of tag objects. This function
+ * just does the messy work of understanding how tags are
+ * stored in nsIMsgDBHdrs.
+ *
+ * @param {nsIMsgDbHdr} aMsgHdr the msgHdr whose tags we want
+ * @return {nsIMsgTag array} a list of tag objects
+ */
+function msgHdrGetTags(aMsgHdr) {
+ let keywords = aMsgHdr.getStringProperty("keywords");
+ let keywordList = keywords.split(' ');
+ let keywordMap = {};
+ for (let keyword of keywordList) {
+ keywordMap[keyword] = true;
+ }
+
+ let tagArray = MailServices.tags.getAllTags({});
+ let tags = tagArray.filter(tag => tag.key in keywordMap);
+ return tags;
+}
+
+/**
+ * Set the tags for a given msgHdr.
+ *
+ * @param {nsIMsgDBHdr} aMsgHdr
+ * @param {nsIMsgTag array} aTags
+ */
+function msgHdrSetTags(aMsgHdr, aTags) {
+ let oldTagList = msgHdrGetTags(aMsgHdr);
+ let oldTags = {}; // hashmap
+ for (let tag of oldTagList)
+ oldTags[tag.key] = null;
+
+ let newTags = {};
+ let newTagList = aTags;
+ for (let tag of newTagList)
+ newTags[tag.key] = null;
+
+ let toAdd = newTagList.filter(x => !(x.key in oldTags)).map(x => x.key);
+ let toRemove = oldTagList.filter(x => !(x.key in newTags)).map(x => x.key);
+
+ let folder = aMsgHdr.folder;
+ let msgHdr = toXPCOMArray([aMsgHdr], Ci.nsIMutableArray);
+ folder.addKeywordsToMessages(msgHdr, toAdd.join(" "));
+ folder.removeKeywordsFromMessages(msgHdr, toRemove.join(" "));
+ aMsgHdr.folder.msgDatabase = null;
+}
+
+/**
+ * Mark an array of msgHdrs read (or unread)
+ * @param {nsIMsgDbHdr array} msgHdrs The message headers
+ * @param {bool} read True to mark them read, false to mark them unread
+ */
+function msgHdrsMarkAsRead(msgHdrs, read) {
+ let pending = {};
+ for (let msgHdr of msgHdrs) {
+ if (msgHdr.isRead == read)
+ continue;
+ if (!pending[msgHdr.folder.URI]) {
+ pending[msgHdr.folder.URI] = {
+ folder: msgHdr.folder,
+ msgs: Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray)
+ };
+ }
+ pending[msgHdr.folder.URI].msgs.appendElement(msgHdr, false);
+ }
+ for (let [uri, {
+ folder, msgs
+ }] of entries(pending)) {
+ folder.markMessagesRead(msgs, read);
+ folder.msgDatabase = null; /* don't leak */
+ }
+}
+
+/**
+ * Delete a set of messages.
+ * @param {nsIMsgDbHdr array} msgHdrs The message headers
+ */
+function msgHdrsDelete(msgHdrs) {
+ let pending = {};
+ for (let msgHdr of msgHdrs) {
+ if (!pending[msgHdr.folder.URI]) {
+ pending[msgHdr.folder.URI] = {
+ folder: msgHdr.folder,
+ msgs: Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray)
+ };
+ }
+ pending[msgHdr.folder.URI].msgs.appendElement(msgHdr, false);
+ }
+ for (let [uri, {
+ folder, msgs
+ }] of entries(pending)) {
+ folder.deleteMessages(msgs, getMail3Pane().msgWindow, false, false, null, true);
+ folder.msgDatabase = null; /* don't leak */
+ }
+}
+
+/**
+ * Get the main Thunderbird window. Used heavily to get a reference to globals
+ * that are defined in mail/base/content/.
+ * @return The window object for the main window.
+ */
+function getMail3Pane() {
+ return Cc["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Ci.nsIWindowMediator)
+ .getMostRecentWindow("mail:3pane");
+}
+
+/**
+ * Archive a set of messages
+ * @param {nsIMsgDbHdr array} msgHdrs The message headers
+ */
+function msgHdrsArchive(msgHdrs) {
+ /* See
+ * http://mxr.mozilla.org/comm-central/source/suite/mailnews/mailWindowOverlay.js#1337
+ *
+ * The window is here because otherwise we don't have access to
+ * BatchMessageMover.
+ * */
+ let mail3PaneWindow = getMail3Pane();
+ let batchMover = new mail3PaneWindow.BatchMessageMover();
+ batchMover.archiveMessages(msgHdrs.filter(
+ x => !msgHdrIsArchive(x) && getMail3Pane().getIdentityForHeader(x).archiveEnabled
+ ));
+}
+
+/**
+ * Tell if a message is an RSS feed iteme
+ * @param {nsIMsgDbHdr} msgHdr The message header
+ * @return {Bool}
+ */
+function msgHdrIsRss(msgHdr) {
+ return msgHdr.folder.server instanceof Ci.nsIRssIncomingServer;
+}
+
+/**
+ * Tell if a message is a NNTP message
+ * @param {nsIMsgDbHdr} msgHdr The message header
+ * @return {Bool}
+ */
+function msgHdrIsNntp(msgHdr) {
+ return msgHdr.folder.server instanceof Ci.nsINntpIncomingServer;
+}
+
+/**
+ * Tell if a message has been marked as junk.
+ * @param {nsIMsgDbHdr} msgHdr The message header
+ * @return {Bool}
+ */
+function msgHdrIsJunk(aMsgHdr) {
+ return aMsgHdr.getStringProperty("junkscore") == Ci.nsIJunkMailPlugin.IS_SPAM_SCORE;
+}
+
+/**
+ * Recycling the HeaderHandlerBase from mimemsg.js
+ */
+function HeaderHandler(aHeaders) {
+ this.headers = aHeaders;
+}
+
+HeaderHandler.prototype = {
+ __proto__: MimeMessage.prototype.__proto__, // == HeaderHandlerBase
+};
+
+/**
+ * Creates a stream listener that will call k once done, passing it the string
+ * that has been read.
+ */
+function createStreamListener(k) {
+ return {
+ _data: "",
+ _stream: null,
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIStreamListener, Ci.nsIRequestObserver]),
+
+ // nsIRequestObserver
+ onStartRequest: function(aRequest) {},
+ onStopRequest: function(aRequest, aStatusCode) {
+ try {
+ k(this._data);
+ }
+ catch (e) {
+ dump("Error inside stream listener:\n" + e + "\n");
+ }
+ },
+
+ // nsIStreamListener
+ onDataAvailable: function(aRequest, dummy, aInputStream, aOffset, aCount) {
+ if (isPlatformNewerThan("67")) {
+ aInputStream = dummy;
+ aCount = aOffset;
+ }
+ if (this._stream == null) {
+ this._stream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream);
+ this._stream.init(aInputStream);
+ }
+ this._data += this._stream.read(aCount);
+ }
+ };
+}
+
+/**
+ * @param aMsgHdr The message header whose headers you want
+ * @param k A function that takes a HeaderHandler object (see mimemsg.js).
+ * Such an object has a get function, a has function. It has a header property,
+ * whose keys are lowercased header names, and whose values are list of
+ * strings corresponding to the multiple entries found for that header.
+ */
+function msgHdrGetHeaders(aMsgHdr, k) {
+ let uri = msgHdrGetUri(aMsgHdr);
+ let messageService = MailServices.messenger.messageServiceFromURI(uri);
+
+ let fallback = () =>
+ MsgHdrToMimeMessage(aMsgHdr, null, function(aMsgHdr, aMimeMsg) {
+ k(aMimeMsg);
+ }, true, {
+ partsOnDemand: true,
+ });
+
+ // This is intentionally disabled because there's a bug in Thunderbird that
+ // renders the supposedly-useful streamHeaders function unusable.
+ if (false && "streamHeaders" in messageService) {
+ try {
+ messageService.streamHeaders(uri, createStreamListener(aRawString => {
+ let re = /\r?\n\s+/g;
+ let str = aRawString.replace(re, " ");
+ let lines = str.split(/\r?\n/);
+ let obj = {};
+ for (let line of lines) {
+ let i = line.indexOf(":");
+ if (i < 0)
+ continue;
+ let k = line.substring(0, i).toLowerCase();
+ let v = line.substring(i + 1).trim();
+ if (!(k in obj))
+ obj[k] = [];
+ obj[k].push(v);
+ }
+ k(new HeaderHandler(obj));
+ }), null, true);
+ }
+ catch (e) {
+ fallback();
+ }
+ }
+ else {
+ fallback();
+ }
+}
+
+/**
+ * @param aMsgHdrs The messages to modify
+ * @param aTransformer A function which takes the input data, modifies it, and
+ * returns the corresponding data. This is the _raw_ contents of the message.
+ */
+function msgHdrsModifyRaw(aMsgHdrs, aTransformer) {
+ let toCopy = [];
+ let toDelete = [];
+ let copyNext = () => {
+ dump("msgHdrModifyRaw: copying next\n");
+ let obj = toCopy.pop();
+ if (!obj) {
+ msgHdrsDelete(toDelete);
+ return;
+ }
+
+ let {
+ msgHdr, tempFile
+ } = obj;
+
+ EnigmailCompat.copyFileToMailFolder(
+ tempFile,
+ msgHdr.folder,
+ msgHdr.flags,
+ msgHdr.getStringProperty("keywords"), {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIMsgCopyServiceListener]),
+
+ OnStartCopy: function() {},
+ OnProgress: function(aProgress, aProgressMax) {},
+ SetMessageKey: function(aKey) {},
+ GetMessageId: function(aMessageId) {},
+ OnStopCopy: function(aStatus) {
+ if (NS_SUCCEEDED(aStatus)) {
+ dump("msgHdrModifyRaw: copied successfully\n");
+ toDelete.push(msgHdr);
+ tempFile.remove(false);
+ }
+ copyNext();
+ }
+ },
+ null
+ );
+ };
+
+ let count = aMsgHdrs.length;
+ let tick = function() {
+ if (--count == 0)
+ copyNext();
+ }
+
+ for (let aMsgHdr of aMsgHdrs) {
+ let msgHdr = aMsgHdr;
+ let uri = msgHdrGetUri(msgHdr);
+ let messageService = MailServices.messenger.messageServiceFromURI(uri);
+ messageService.streamMessage(uri, createStreamListener(function(aRawString) {
+ let data = aTransformer(aRawString);
+ if (!data) {
+ dump("msgHdrModifyRaw: no data, aborting\n");
+ return;
+ }
+
+ let tempFile = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ tempFile.append("rethread.eml");
+ tempFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("0600", 8));
+
+ let stream = Cc["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Ci.nsIFileOutputStream);
+ stream.init(tempFile, PR_WRONLY, parseInt("0600", 8), 0);
+ stream.write(data, data.length);
+ stream.close();
+
+ dump("msgHdrModifyRaw: wrote to file\n");
+ toCopy.push({
+ tempFile: tempFile,
+ msgHdr: msgHdr
+ });
+ tick();
+ }), null, null, false, "");
+ }
+}
+
+
+/**
+ * return true, if plafform is newer than or equal a given version
+ */
+function isPlatformNewerThan(requestedVersion) {
+ let vc = Cc["@mozilla.org/xpcom/version-comparator;1"].getService(Ci.nsIVersionComparator);
+ let appVer = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo).platformVersion;
+
+ return vc.compare(appVer, requestedVersion) >= 0;
+}
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/streams.jsm
@@ -0,0 +1,170 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailStreams"];
+
+const EnigmailCompat = ChromeUtils.import("chrome://openpgp/content/modules/compat.jsm").EnigmailCompat;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailTimer = ChromeUtils.import("chrome://openpgp/content/modules/timer.jsm").EnigmailTimer;
+const Services = ChromeUtils.import("resource://gre/modules/Services.jsm").Services;
+const NetUtil = ChromeUtils.import("resource://gre/modules/NetUtil.jsm").NetUtil;
+
+const NS_STRING_INPUT_STREAM_CONTRACTID = "@mozilla.org/io/string-input-stream;1";
+const NS_INPUT_STREAM_CHNL_CONTRACTID = "@mozilla.org/network/input-stream-channel;1";
+const IOSERVICE_CONTRACTID = "@mozilla.org/network/io-service;1";
+
+var EnigmailStreams = {
+
+ /**
+ * Create a new channel from a URL or URI.
+ *
+ * @param url: String, nsIURI or nsIFile - URL specification
+ *
+ * @return: channel
+ */
+ createChannel: function(url) {
+ let c = NetUtil.newChannel({
+ uri: url,
+ loadUsingSystemPrincipal: true
+ });
+
+ return c;
+ },
+
+
+ /**
+ * create an nsIStreamListener object to read String data from an nsIInputStream
+ *
+ * @onStopCallback: Function - function(data) that is called when the stream has stopped
+ * string data is passed as |data|
+ *
+ * @return: the nsIStreamListener to pass to the stream
+ */
+ newStringStreamListener: function(onStopCallback) {
+ EnigmailLog.DEBUG("enigmailCommon.jsm: newStreamListener\n");
+
+ let listener = {
+ data: "",
+ inStream: Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream),
+ _onStopCallback: onStopCallback,
+ QueryInterface: EnigmailCompat.generateQI([Ci.nsIStreamListener, Ci.nsIRequestObserver]),
+
+ onStartRequest: function(channel) {
+ // EnigmailLog.DEBUG("enigmailCommon.jsm: stringListener.onStartRequest\n");
+ },
+
+ onStopRequest: function(channel, status) {
+ // EnigmailLog.DEBUG("enigmailCommon.jsm: stringListener.onStopRequest: "+ctxt+"\n");
+ this.inStream = null;
+ var cbFunc = this._onStopCallback;
+ var cbData = this.data;
+
+ EnigmailTimer.setTimeout(function _cb() {
+ cbFunc(cbData);
+ });
+ }
+ };
+
+ if (EnigmailCompat.isMessageUriInPgpMime()) {
+ // TB >= 67
+ listener.onDataAvailable = function(req, stream, offset, count) {
+ // EnigmailLog.DEBUG("enigmailCommon.jsm: stringListener.onDataAvailable: "+count+"\n");
+ this.inStream.setInputStream(stream);
+ this.data += this.inStream.readBytes(count);
+ };
+ } else {
+ listener.onDataAvailable = function(req, ctxt, stream, offset, count) {
+ // EnigmailLog.DEBUG("enigmailCommon.jsm: stringListener.onDataAvailable: "+count+"\n");
+ this.inStream.setInputStream(stream);
+ this.data += this.inStream.readBytes(count);
+ };
+ }
+
+ return listener;
+ },
+
+ /**
+ * create a nsIInputStream object that is fed with string data
+ *
+ * @uri: nsIURI - object representing the URI that will deliver the data
+ * @contentType: String - the content type as specified in nsIChannel
+ * @contentCharset: String - the character set; automatically determined if null
+ * @data: String - the data to feed to the stream
+ * @loadInfo nsILoadInfo - loadInfo (optional)
+ *
+ * @return nsIChannel object
+ */
+ newStringChannel: function(uri, contentType, contentCharset, data, loadInfo) {
+ EnigmailLog.DEBUG("enigmailCommon.jsm: newStringChannel\n");
+
+ if (!loadInfo) {
+ loadInfo = createLoadInfo();
+ }
+
+ const inputStream = Cc[NS_STRING_INPUT_STREAM_CONTRACTID].createInstance(Ci.nsIStringInputStream);
+ inputStream.setData(data, -1);
+
+ if (!contentCharset || contentCharset.length === 0) {
+ const ioServ = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+ const netUtil = ioServ.QueryInterface(Ci.nsINetUtil);
+ const newCharset = {};
+ const hadCharset = {};
+ let mimeType;
+ mimeType = netUtil.parseResponseContentType(contentType, newCharset, hadCharset);
+ contentCharset = newCharset.value;
+ }
+
+ let isc = Cc[NS_INPUT_STREAM_CHNL_CONTRACTID].createInstance(Ci.nsIInputStreamChannel);
+ isc.QueryInterface(Ci.nsIChannel);
+ isc.setURI(uri);
+ isc.loadInfo = loadInfo;
+ isc.contentStream = inputStream;
+
+ if (contentType && contentType.length) isc.contentType = contentType;
+ if (contentCharset && contentCharset.length) isc.contentCharset = contentCharset;
+
+ EnigmailLog.DEBUG("enigmailCommon.jsm: newStringChannel - done\n");
+
+ return isc;
+ },
+
+ newFileChannel: function(uri, file, contentType, deleteOnClose) {
+ EnigmailLog.DEBUG("enigmailCommon.jsm: newFileChannel for '" + file.path + "'\n");
+
+ let inputStream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);
+ let behaviorFlags = Ci.nsIFileInputStream.CLOSE_ON_EOF;
+ if (deleteOnClose) {
+ behaviorFlags |= Ci.nsIFileInputStream.DELETE_ON_CLOSE;
+ }
+ const ioFlags = 0x01; // readonly
+ const perm = 0;
+ inputStream.init(file, ioFlags, perm, behaviorFlags);
+
+ let isc = Cc[NS_INPUT_STREAM_CHNL_CONTRACTID].createInstance(Ci.nsIInputStreamChannel);
+ isc.QueryInterface(Ci.nsIChannel);
+ isc.contentDisposition = Ci.nsIChannel.DISPOSITION_ATTACHMENT;
+ isc.loadInfo = createLoadInfo();
+ isc.setURI(uri);
+ isc.contentStream = inputStream;
+
+ if (contentType && contentType.length) isc.contentType = contentType;
+
+ EnigmailLog.DEBUG("enigmailCommon.jsm: newStringChannel - done\n");
+
+ return isc;
+ }
+};
+
+function createLoadInfo() {
+ let c = NetUtil.newChannel({
+ uri: "chrome://openpgp/content/",
+ loadUsingSystemPrincipal: true
+ });
+
+ return c.loadInfo;
+}
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/subprocess.jsm
@@ -0,0 +1,385 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+/*
+ * Import into a JS component using
+ * 'ChromeUtils.import("chrome://openpgp/content/modules/subprocess.jsm");'
+ *
+ * This object allows to start a process, and read/write data to/from it
+ * using stdin/stdout/stderr streams.
+ * Usage example:
+ *
+ * var p = subprocess.call({
+ * command: '/bin/foo',
+ * arguments: ['-v', 'foo'],
+ * environment: [ "XYZ=abc", "MYVAR=def" ],
+ * //stdin: "some value to write to stdin\nfoobar",
+ * stdin: function(stdin) {
+ * stdin.write("some value to write to stdin\nfoobar");
+ * stdin.close();
+ * },
+ * stdout: function(data) {
+ * dump("got data on stdout:" + data + "\n");
+ * },
+ * stderr: function(data) {
+ * dump("got data on stderr:" + data + "\n");
+ * },
+ * done: function(result) {
+ * dump("process terminated with " + result.exitCode + "\n");
+ * },
+ * mergeStderr: false
+ * });
+ * p.wait(); // wait for the subprocess to terminate
+ * // this will block the main thread,
+ * // only do if you can wait that long
+ *
+ *
+ * Description of parameters:
+ * --------------------------
+ * Apart from <command>, all arguments are optional.
+ *
+ * command: either a |nsIFile| object pointing to an executable file or a
+ * String containing the platform-dependent path to an executable
+ * file.
+ *
+ * arguments: optional string array containing the arguments to the command.
+ *
+ * environment: optional string array containing environment variables to pass
+ * to the command. The array elements must have the form
+ * "VAR=data". Please note that if environment is defined, it
+ * replaces any existing environment variables for the subprocess.
+ *
+ * stdin: optional input data for the process to be passed on standard
+ * input. stdin can either be a string or a function.
+ * A |string| gets written to stdin and stdin gets closed;
+ * A |function| gets passed an object with write and close function.
+ * Please note that the write() function will return almost immediately;
+ * data is always written asynchronously on a separate thread.
+ *
+ * stdout: an optional function that can receive output data from the
+ * process. The stdout-function is called asynchronously; it can be
+ * called mutliple times during the execution of a process.
+ * At a minimum at each occurance of \n or \r.
+ * Please note that null-characters might need to be escaped
+ * with something like 'data.replace(/\0/g, "\\0");'.
+ *
+ * stderr: an optional function that can receive stderr data from the
+ * process. The stderr-function is called asynchronously; it can be
+ * called mutliple times during the execution of a process. Please
+ * note that null-characters might need to be escaped with
+ * something like 'data.replace(/\0/g, "\\0");'.
+ * (on windows it only gets called once right now)
+ *
+ *
+ * done: optional function that is called when the process has terminated.
+ * The exit code from the process available via result.exitCode. If
+ * stdout is not defined, then the output from stdout is available
+ * via result.stdout. stderr data is in result.stderr
+ * done() is guaranteed to be called before .wait() finishes
+ *
+ * mergeStderr: optional boolean value. If true, stderr is merged with stdout;
+ * no data will be provided to stderr. Default is false.
+ *
+ *
+ * Description of object returned by subprocess.call(...)
+ * ------------------------------------------------------
+ * The object returned by subprocess.call offers a few methods that can be
+ * executed:
+ *
+ * wait(): waits for the subprocess to terminate. It is not required to use
+ * wait; done will be called in any case when the subprocess terminated.
+ *
+ * kill(hardKill): kill the subprocess. Any open pipes will be closed and
+ * done will be called.
+ * hardKill [ignored on Windows]:
+ * - false: signal the process terminate (SIGTERM)
+ * - true: kill the process (SIGKILL)
+ *
+ *
+ * Other methods in subprocess
+ * ---------------------------
+ *
+ * registerDebugHandler(functionRef): register a handler that is called to get
+ * debugging information
+ * registerLogHandler(functionRef): register a handler that is called to get error
+ * messages
+ *
+ * example:
+ * subprocess.registerLogHandler( function(s) { dump(s); } );
+ */
+
+'use strict';
+
+const SubprocessMain = ChromeUtils.import("chrome://openpgp/content/modules/enigmailprocess_main.jsm").SubprocessMain;
+const Services = ChromeUtils.import("resource://gre/modules/Services.jsm").Services;
+
+var EXPORTED_SYMBOLS = ["subprocess"];
+
+const DEFAULT_ENVIRONMENT = [];
+
+var gDebugFunction = null;
+var gErrorFunction = null;
+var gRunningProcesses = []; // Array with all running subprocesses
+
+function write(pipe, data) {
+ let buffer = new Uint8Array(Array.from(data, c => c.charCodeAt(0)));
+ return pipe.write(buffer);
+}
+
+function arrayBufferToString(buffer) {
+ const MAXLEN = 102400;
+
+ let uArr = new Uint8Array(buffer);
+ let ret = "";
+ let len = buffer.byteLength;
+
+ for (let j = 0; j < Math.floor(len / MAXLEN) + 1; j++) {
+ ret += String.fromCharCode.apply(null, uArr.subarray(j * MAXLEN, ((j + 1) * MAXLEN)));
+ }
+
+ return ret;
+}
+
+async function read(pipe) {
+ let buffer = await pipe.read();
+
+ try {
+ if (buffer.byteLength > 0) {
+ return arrayBufferToString(buffer);
+ }
+ } catch (ex) {
+ DEBUG_LOG("err: " + ex.toString());
+ }
+ return "";
+}
+
+
+var readAllData = async function(pipe, read, callback) {
+ /* eslint no-cond-assign: 0 */
+ let string;
+ while (string = await read(pipe)) {
+ callback(string);
+ }
+};
+
+
+function removeProcRef(proc) {
+ if (proc) {
+ let i = gRunningProcesses.indexOf(proc);
+ if (i >= 0) {
+ gRunningProcesses.splice(i, 1);
+ }
+ }
+}
+
+var subprocess = {
+ registerLogHandler: function(func) {
+ gErrorFunction = func;
+ },
+
+ registerDebugHandler: function(func) {
+ gDebugFunction = func;
+ },
+
+ call: function(options) {
+
+ let resolved = null;
+ let promises = [];
+ let inputPromises = [];
+ let stdinClosed = false;
+ let stdoutData = "";
+ let stderrData = "";
+
+ let formattedStack = Components.stack.formattedStack;
+
+ function writePipe(pipe, value) {
+ let p = write(pipe, value);
+ promises.push(p);
+ inputPromises.push(p);
+ }
+
+ function subProcessThen(proc) {
+ gRunningProcesses.push(proc);
+
+ if (typeof options.stdin === "function") {
+ // Some callers (e.g. child_process.js) depend on this
+ // being called synchronously.
+ options.stdin({
+ write(val) {
+ writePipe(proc.stdin, val);
+ },
+
+ close() {
+ Promise.all(inputPromises).then(() => {
+ if (!stdinClosed) {
+ stdinClosed = true;
+ proc.stdin.close();
+ }
+ });
+ }
+ });
+
+ } else {
+ if (typeof options.stdin === "string") {
+ DEBUG_LOG("write Stdin");
+ writePipe(proc.stdin, options.stdin);
+ }
+
+ Promise.all(inputPromises).then(() => {
+ proc.stdin.close();
+ });
+ }
+
+
+ promises.push(
+ readAllData(proc.stdout, read, data => {
+ DEBUG_LOG("Got Stdout: " + data.length + "\n");
+ if (typeof options.stdout === "function") {
+ try {
+ options.stdout(data);
+ } catch (ex) {}
+ } else
+ stdoutData += data;
+ }));
+
+ if (!options.mergeStderr) {
+ promises.push(
+ readAllData(proc.stderr, read, data => {
+ DEBUG_LOG("Got Stderr: " + data.length + "\n");
+ if (typeof options.stderr === "function") {
+ try {
+ options.stderr(data);
+ } catch (ex) {}
+ } else
+ stderrData += data;
+
+ }));
+ }
+
+ Promise.all(promises)
+ .then(() => proc.wait())
+ .then(result => {
+ DEBUG_LOG("Complete: " + result.exitCode + "\n");
+ removeProcRef(proc);
+ if (gRunningProcesses.indexOf(proc) >= 0) {
+
+ }
+ if (result.exitCode === null) result.exitCode = -1;
+ resolved = result.exitCode;
+ if (typeof options.done === "function") {
+ try {
+ options.done({
+ exitCode: result.exitCode,
+ stdout: stdoutData,
+ stderr: stderrData
+ });
+ } catch (ex) {}
+ }
+ })
+ .catch(error => {
+ resolved = -1;
+ let errStr = "";
+ if (typeof error === "string") {
+ errStr = error;
+ } else if (error) {
+ for (let i in error) {
+ errStr += "\n" + i + ": " + error[i];
+ }
+ }
+
+ ERROR_LOG(errStr);
+ throw ("subprocess.jsm: caught error: " + errStr);
+ });
+
+ }
+
+ let opts = {};
+ if (options.mergeStderr) {
+ opts.stderr = "stdout";
+ } else {
+ opts.stderr = "pipe";
+ }
+
+ if (options.command instanceof Ci.nsIFile) {
+ opts.command = options.command.path;
+ } else {
+ opts.command = options.command;
+ }
+
+ if (options.workdir) {
+ opts.workdir = options.workdir;
+ }
+
+ opts.arguments = options.arguments || [];
+
+
+ // Set up environment
+
+ let envVars = options.environment || DEFAULT_ENVIRONMENT;
+ if (envVars.length) {
+ let environment = {};
+ for (let val of envVars) {
+ let idx = val.indexOf("=");
+ if (idx >= 0)
+ environment[val.slice(0, idx)] = val.slice(idx + 1);
+ }
+
+ opts.environment = environment;
+ }
+
+ let subproc = SubprocessMain.call(opts).then(subProcessThen).catch(
+ error => {
+ resolved = -1;
+ let errStr = formattedStack;
+ throw ("subprocess.jsm: launch error: " + errStr + 'error: ' +
+ error + "\n" + JSON.stringify(error));
+ }
+ );
+
+ return {
+ wait: function() {
+ let mainThread = Services.tm.mainThread;
+ while (resolved === null)
+ mainThread.processNextEvent(true);
+
+ return resolved;
+ },
+
+ kill: function(hard = false) {
+ subproc.then(proc => {
+ proc.kill(hard ? 0 : undefined);
+ removeProcRef();
+ });
+ }
+ };
+ },
+
+
+ /**
+ * on shutdown kill all still running child processes
+ */
+ onShutdown: function() {
+ // create a copy of the array because gRunningProcesses will
+ // get altered during kill()
+ let procs = gRunningProcesses.map(x => x);
+
+ for (let i = 0; i < procs.length; i++) {
+ if (procs[i] && ("kill" in procs[i])) {
+ procs[i].kill(true);
+ }
+ }
+ }
+};
+
+function DEBUG_LOG(str) {
+ if (gDebugFunction) {
+ gDebugFunction("subprocess.jsm: " + str + "\n");
+ }
+}
+
+function ERROR_LOG(str) {
+ if (gErrorFunction) {
+ gErrorFunction("subprocess.jsm: " + str + "\n");
+ }
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/system.jsm
@@ -0,0 +1,288 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailSystem"];
+
+
+
+
+
+const ctypes = ChromeUtils.import("resource://gre/modules/ctypes.jsm").ctypes;
+const EnigmailOS = ChromeUtils.import("chrome://openpgp/content/modules/os.jsm").EnigmailOS;
+const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
+const subprocess = ChromeUtils.import("chrome://openpgp/content/modules/subprocess.jsm").subprocess;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs.jsm").EnigmailPrefs;
+
+var gKernel32Dll = null;
+var gSystemCharset = null;
+
+const CODEPAGE_MAPPING = {
+ "437": "ISO-8859-1",
+ "855": "IBM855",
+ "866": "IBM866",
+ "874": "ISO-8859-11",
+ "932": "Shift_JIS",
+ "936": "GB2312",
+ "950": "BIG5",
+ "1200": "UTF-16LE",
+ "1201": "UTF-16BE",
+ "1250": "windows-1250",
+ "1251": "windows-1251",
+ "1252": "windows-1252",
+ "1253": "windows-1253",
+ "1254": "windows-1254",
+ "1255": "windows-1255",
+ "1256": "windows-1256",
+ "1257": "windows-1257",
+ "1258": "windows-1258",
+ "20866": "KOI8-R",
+ "20932": "EUC-JP",
+ "28591": "ISO-8859-1",
+ "28592": "ISO-8859-2",
+ "28593": "ISO-8859-3",
+ "28594": "ISO-8859-4",
+ "28595": "ISO-8859-5",
+ "28596": "ISO-8859-6",
+ "28597": "ISO-8859-7",
+ "28598": "ISO-8859-8",
+ "28599": "ISO-8859-9",
+ "28603": "ISO-8859-13",
+ "28605": "ISO-8859-15",
+ "38598": "ISO-8859-8",
+ "50220": "ISO-2022-JP",
+ "50221": "ISO-2022-JP",
+ "50222": "ISO-2022-JP",
+ "50225": "ISO-2022-KR",
+ "50227": "ISO-2022-CN",
+ "50229": "ISO-2022-CN",
+ "51932": "EUC-JP",
+ "51949": "EUC-KR",
+ "52936": "HZ-GB2312",
+ "65000": "UTF-7",
+ "65001": "UTF-8"
+};
+
+
+/**
+ * Get the default codepage that is set on Windows (which equals to the chatset of the console output of gpg)
+ */
+function getWindowsCopdepage() {
+ EnigmailLog.DEBUG("system.jsm: getWindowsCopdepage\n");
+
+ if (EnigmailPrefs.getPref("gpgLocaleEn")) {
+ return "437";
+ }
+
+ let output = "";
+ let env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
+ let sysRoot = env.get("SystemRoot");
+
+ if (!sysRoot || sysRoot.length === 0) {
+ sysRoot = "C:\\windows";
+ }
+
+ try {
+ let p = subprocess.call({
+ command: sysRoot + "\\system32\\chcp.com",
+ arguments: [],
+ environment: [],
+ charset: null,
+ mergeStderr: false,
+ stdout: function(data) {
+ output += data;
+ }
+ });
+ p.wait();
+
+ output = output.replace(/[\r\n]/g, "");
+ output = output.replace(/^(.*[: ])([0-9]+)([^0-9].*)?$/, "$2");
+ }
+ catch (ex) {
+ output = "437";
+ }
+
+ return output;
+}
+
+/**
+ * Get the charset defined with LC_ALL or locale. That's the charset used by gpg console output
+ */
+function getUnixCharset() {
+ EnigmailLog.DEBUG("system.jsm: getUnixCharset\n");
+ let env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
+ let lc = env.get("LC_ALL");
+
+
+ if (lc.length === 0) {
+ let places = [
+ "/usr/bin/locale",
+ "/usr/local/bin/locale",
+ "/opt/bin/locale"
+ ];
+ var localeFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+
+ for (let i = 0; i < places.length; i++) {
+ localeFile.initWithPath(places[i]);
+ if (localeFile.exists()) break;
+ }
+
+ if (!localeFile.exists()) return "utf-8";
+
+ let output = "";
+
+ let p = subprocess.call({
+ command: localeFile,
+ arguments: [],
+ environment: [],
+ charset: null,
+ mergeStderr: false,
+ stdout: function(data) {
+ output += data;
+ }
+ });
+ p.wait();
+
+ let m = output.match(/^(LC_ALL=)(.*)$/m);
+ if (m && m.length > 2) {
+ lc = m[2].replace(/"/g, "");
+ }
+ else return "utf-8";
+ }
+
+ let i = lc.search(/[.@]/);
+
+ if (i < 0) return "utf-8";
+
+ lc = lc.substr(i + 1);
+
+ return lc;
+
+}
+
+function getKernel32Dll() {
+ if (!gKernel32Dll) {
+ if (EnigmailOS.isWin32) {
+ gKernel32Dll = ctypes.open("kernel32.dll");
+ }
+ else {
+ return null;
+ }
+ }
+
+ return gKernel32Dll;
+}
+
+
+var EnigmailSystem = {
+
+ determineSystemCharset: function() {
+ EnigmailLog.DEBUG("system.jsm: determineSystemCharset\n");
+
+ if (!gSystemCharset) {
+ if (EnigmailOS.isWin32) {
+ gSystemCharset = getWindowsCopdepage();
+ }
+ else {
+ gSystemCharset = getUnixCharset();
+ }
+ }
+
+ EnigmailLog.DEBUG("system.jsm: determineSystemCharset: charset='" + gSystemCharset + "'\n");
+ return gSystemCharset;
+ },
+
+ /**
+ * Convert system output coming in a native charset into Unicode (Gecko-platfrom)
+ * applying an appropriate charset conversion
+ *
+ * @param str String - input string in native charset
+ * @param cs String - [Optional] character set (Unix), or codepage (Windows).
+ * If not specified, determine the system default.
+ *
+ * @param String - output in Unicode format. If something failed, the unmodified
+ * input isreturned.
+ */
+
+ convertNativeToUnicode: function(str, cs) {
+ try {
+ if (!cs) cs = this.determineSystemCharset();
+
+ if (EnigmailOS.isWin32) {
+ if (cs in CODEPAGE_MAPPING) {
+ return EnigmailData.convertToUnicode(str, CODEPAGE_MAPPING[cs]);
+ }
+ else {
+ let charSetNum = Number(cs);
+ if (Number.isNaN(charSetNum)) {
+ return EnigmailData.convertToUnicode(str, cs);
+ }
+ else
+ return EnigmailData.convertToUnicode(this.winConvertNativeToUnichar(str, Number(cs)), "UTF-8");
+ }
+ }
+ else {
+ return EnigmailData.convertToUnicode(str, cs);
+ }
+ }
+ catch (ex) {
+ EnigmailLog.DEBUG("system.jsm: convertNativeToUnicode: exception +" + ex.toString() + "\n");
+
+ return str;
+ }
+ },
+
+ /**
+ * Convert from native Windows output (often Codepage 437) to a Mozilla Unichar string
+ *
+ * @param byteStr: String - the data to convert in the current Windows codepage
+ *
+ * @return String: the Unicode string directly display-able
+ */
+ winConvertNativeToUnichar: function(byteStr, codePage) {
+ /*
+ int MultiByteToWideChar(
+ _In_ UINT CodePage,
+ _In_ DWORD dwFlags,
+ _In_ LPCSTR lpMultiByteStr,
+ _In_ int cbMultiByte,
+ _Out_opt_ LPWSTR lpWideCharStr,
+ _In_ int cchWideChar
+ );
+ */
+
+ if (!getKernel32Dll()) {
+ return byteStr;
+ }
+
+ var multiByteToWideChar = gKernel32Dll.declare("MultiByteToWideChar",
+ ctypes.winapi_abi,
+ ctypes.int, // return value
+ ctypes.unsigned_int, // Codepage
+ ctypes.uint32_t, // dwFlags
+ ctypes.char.ptr, // input string
+ ctypes.int, // cbMultiByte
+ ctypes.jschar.ptr, // widechar string
+ ctypes.int // ccWideChar
+ );
+
+ let n = multiByteToWideChar(codePage, 0, byteStr, byteStr.length, null, 0);
+
+ if (n > 0) {
+ let OutStrType = ctypes.jschar.array(n + 1);
+ let outStr = new OutStrType();
+
+ multiByteToWideChar(codePage, 0, byteStr, byteStr.length, outStr.addressOfElement(0), n);
+
+ let r = new RegExp(String.fromCharCode(9516), "g");
+ return outStr.readString().replace(r, "");
+
+ }
+ else
+ return byteStr;
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/time.jsm
@@ -0,0 +1,62 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailTime"];
+
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+
+const DATE_2DIGIT = "2-digit";
+const DATE_4DIGIT = "numeric";
+
+var EnigmailTime = {
+ /**
+ * Transform a Unix-Timestamp to a human-readable date/time string
+ *
+ * @dateNum: Number - Unix timestamp
+ * @withDate: Boolean - if true, include the date in the output
+ * @withTime: Boolean - if true, include the time in the output
+ *
+ * @return: String - formatted date/time string
+ */
+ getDateTime: function(dateNum, withDate, withTime) {
+ if (dateNum && dateNum !== 0) {
+ let dat = new Date(dateNum * 1000);
+ let appLocale = EnigmailLocale.get();
+
+ var options = {};
+
+ if (withDate) {
+ options.day = DATE_2DIGIT;
+ options.month = DATE_2DIGIT;
+ let year = dat.getFullYear();
+ if (year > 2099) {
+ options.year = DATE_4DIGIT;
+ }
+ else {
+ options.year = DATE_2DIGIT;
+ }
+ }
+ if (withTime) {
+ options.hour = DATE_2DIGIT;
+ options.minute = DATE_2DIGIT;
+ }
+
+ let useLocale = appLocale.getCategory("NSILOCALE_TIME").substr(0, 5);
+ useLocale = useLocale.replace(/_/g, "-");
+
+ try {
+ return new Intl.DateTimeFormat(useLocale, options).format(dat);
+ } catch (ex) {
+ return new Intl.DateTimeFormat("en-US", options).format(dat);
+ }
+ }
+ else {
+ return "";
+ }
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/timer.jsm
@@ -0,0 +1,49 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+const EXPORTED_SYMBOLS = ["EnigmailTimer"];
+
+const {
+ setTimeout,
+ clearTimeout
+} = ChromeUtils.import("resource://gre/modules/Timer.jsm");
+
+var EnigmailTimer = {
+ /**
+ * wait a defined number of miliseconds, then call a callback function
+ * asynchronously
+ *
+ * @param callbackFunction: Function - any function specification
+ * @param sleepTimeMs: Number - optional number of miliseconds to delay
+ * (0 if not specified)
+ *
+ * @return Number: timeoutID
+ */
+ setTimeout: function(callbackFunction, sleepTimeMs = 0) {
+
+ let timeoutID;
+
+ function callbackWrapper() {
+ callbackFunction();
+ try {
+ clearTimeout(timeoutID);
+ } catch (ex) {}
+ }
+
+ timeoutID = setTimeout(callbackWrapper, sleepTimeMs);
+
+ return timeoutID;
+ },
+
+ /**
+ * Cancel a timeout callback
+ *
+ * @param Number: timeoutID
+ */
+ clearTimeout: clearTimeout
+};
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/tor.jsm
@@ -0,0 +1,280 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+
+
+
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs.jsm").EnigmailPrefs;
+const EnigmailRNG = ChromeUtils.import("chrome://openpgp/content/modules/rng.jsm").EnigmailRNG;
+const EnigmailVersioning = ChromeUtils.import("chrome://openpgp/content/modules/versioning.jsm").EnigmailVersioning;
+const EnigmailOS = ChromeUtils.import("chrome://openpgp/content/modules/os.jsm").EnigmailOS;
+const EnigmailSocks5Proxy = ChromeUtils.import("chrome://openpgp/content/modules/socks5Proxy.jsm").EnigmailSocks5Proxy;
+const EnigmailGpg = ChromeUtils.import("chrome://openpgp/content/modules/gpg.jsm").EnigmailGpg;
+const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
+const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
+
+const EXPORTED_SYMBOLS = ["EnigmailTor"];
+
+// Minimum for using socks5h:// prefix
+const MINIMUM_CURL_SOCKS5H_VERSION = "7.21.7";
+
+// Minimum for using socks5 proxies with curl
+const MINIMUM_CURL_SOCKS5_PROXY_VERSION = "7.18.0";
+
+const TORSOCKS_VERSION_2 = "2.0.0";
+
+const TOR_SERVICE_PORT_PREF = "torServicePort";
+const TOR_BROWSER_BUNDLE_PORT_PREF = "torBrowserBundlePort";
+const NEW_CURL_PROTOCOL = "socks5h://";
+const OLD_CURL_PROTOCOL = "socks5-hostname://";
+
+const TOR_USER_PREFERENCES = {
+ DOWNLOAD: {
+ requires: "downloadKeyRequireTor",
+ uses: "downloadKeyWithTor",
+ constant: EnigmailConstants.DOWNLOAD_KEY
+ },
+ SEARCH: {
+ requires: "searchKeyRequireTor",
+ uses: "searchKeyWithTor",
+ constant: EnigmailConstants.SEARCH_KEY
+ },
+ UPLOAD: {
+ requires: "uploadKeyRequireTor",
+ uses: "uploadKeyWithTor",
+ constant: EnigmailConstants.UPLOAD_KEY
+ },
+ REFRESH: {
+ requires: "refreshAllKeysRequireTor",
+ uses: "refreshAllKeysWithTor",
+ constant: EnigmailConstants.REFRESH_KEY
+ }
+};
+
+function getAction(actionFlags) {
+ for (let key in TOR_USER_PREFERENCES) {
+ if (TOR_USER_PREFERENCES[key].constant & actionFlags) {
+ return TOR_USER_PREFERENCES[key];
+ }
+ }
+ return null;
+}
+
+/**
+ * Sets user preference about requiring requests only to be made over Tor
+ *
+ * @param actionFlags - long: A Keyserver action flag
+ *
+ * @return true if user has requested gpg requests to be attempted over Tor, false otherwise
+ */
+function isPreferred(actionFlags) {
+ const action = getAction(actionFlags);
+ return EnigmailPrefs.getPref(action.requires) || EnigmailPrefs.getPref(action.uses);
+}
+
+/**
+ * Sets user preference about requiring requests only to be made over Tor
+ *
+ * @param actionFlags - long: A Keyserver action flag
+ *
+ * @return true if user has requested gpg requests ONLY to be attempted over Tor, false otherwise
+ */
+function isRequired(actionFlags) {
+ return EnigmailPrefs.getPref(getAction(actionFlags).requires);
+}
+
+function combineIntoProxyhostURI(protocol, tor) {
+ EnigmailLog.DEBUG("tor.jsm: combineIntoProxyhostURI()\n");
+ return protocol + createRandomCredential() + ":" + createRandomCredential() + "@" + tor.ip + ":" + tor.port;
+}
+
+function gpgProxyArgs(tor, versioning) {
+ EnigmailLog.DEBUG("tor.jsm: gpgProxyArgs()\n");
+ if (EnigmailOS.isDosLike || !versioning.versionFoundMeetsMinimumVersionRequired("curl", MINIMUM_CURL_SOCKS5H_VERSION)) {
+ return combineIntoProxyhostURI(OLD_CURL_PROTOCOL, tor);
+ }
+ else {
+ return combineIntoProxyhostURI(NEW_CURL_PROTOCOL, tor);
+ }
+}
+
+function createHelperArgs(helper, addAuth) {
+ EnigmailLog.DEBUG("tor.jsm: createHelperArgs()\n");
+ let args = [];
+ if (addAuth) {
+ args = ["--user", createRandomCredential(), "--pass", createRandomCredential()];
+ }
+ args.push(EnigmailGpg.agentPath);
+ return args;
+}
+
+function buildEnvVars() {
+ return [
+ "TORSOCKS_USERNAME=" + createRandomCredential(),
+ "TORSOCKS_PASSWORD=" + createRandomCredential()
+ ];
+}
+
+function createRandomCredential() {
+ return EnigmailRNG.generateRandomUint32().toString();
+}
+
+function torOn(portPref) {
+ EnigmailLog.DEBUG("tor.jsm: torOn()\n");
+ if (EnigmailSocks5Proxy.checkTorExists(portPref)) {
+ const port = EnigmailPrefs.getPref(portPref);
+
+ EnigmailLog.CONSOLE("Tor found on IP: " + EnigmailSocks5Proxy.torIpAddr() + ", port: " + port + "\n\n");
+
+ return {
+ ip: EnigmailSocks5Proxy.torIpAddr(),
+ port: port
+ };
+ }
+ return null;
+}
+
+function meetsOSConstraints() {
+ if (EnigmailOS.isDosLike) {
+ return EnigmailGpg.getGpgFeature("socks-on-windows");
+ }
+ else {
+ return EnigmailVersioning.versionFoundMeetsMinimumVersionRequired("curl", MINIMUM_CURL_SOCKS5_PROXY_VERSION);
+ }
+}
+
+function useAuthOverArgs(helper, versioning) {
+ if (helper === "torsocks2") {
+ return versioning.versionFoundMeetsMinimumVersionRequired("torsocks2", TORSOCKS_VERSION_2);
+ }
+ return versioning.versionFoundMeetsMinimumVersionRequired("torsocks", TORSOCKS_VERSION_2);
+}
+
+function usesDirmngr() {
+ return EnigmailGpg.getGpgFeature("supports-dirmngr");
+}
+
+function findTorExecutableHelper(versioning) {
+ EnigmailLog.DEBUG("tor.jsm: findTorExecutableHelper()\n");
+ const helper = EnigmailFiles.resolvePathWithEnv("torsocks2") || EnigmailFiles.resolvePathWithEnv("torsocks");
+ if (helper !== null) {
+ const authOverArgs = useAuthOverArgs(helper, versioning);
+ return {
+ envVars: (authOverArgs ? [] : buildEnvVars()),
+ command: helper,
+ args: createHelperArgs(helper, authOverArgs)
+ };
+ }
+ else {
+ return null;
+ }
+}
+
+/**
+ * Checks if Tor is running on specified ports in preferences for Tor browser bundle and Tor service
+ *
+ * @return true if Tor is running on either port, false if Tor is not running on either
+ */
+function findTor() {
+ EnigmailLog.DEBUG("tor.jsm: findTor()\n");
+ const torOnBrowser = torOn(TOR_BROWSER_BUNDLE_PORT_PREF);
+ if (torOnBrowser !== null) {
+ return torOnBrowser;
+ }
+ return torOn(TOR_SERVICE_PORT_PREF);
+}
+
+const systemCaller = {
+ findTor: findTor,
+ findTorExecutableHelper: findTorExecutableHelper
+};
+
+function buildSocksProperties(tor) {
+ EnigmailLog.DEBUG("tor.jsm: buildSocksProperties()\n");
+ return {
+ command: "gpg",
+ args: gpgProxyArgs(tor, EnigmailVersioning),
+ envVars: []
+ };
+}
+
+function torNotAvailableProperties() {
+ EnigmailLog.DEBUG("tor.jsm: torNotAvailableProperties()\n");
+ return {
+ isAvailable: false,
+ useTorMode: false,
+ socks: null,
+ helper: null
+ };
+}
+
+/**
+ * Constructs object with properites about how we will use tor for key refreshes
+ *
+ * @param system - object with functions to locate Tor and Tor helpers
+ *
+ * @return object with
+ * isAvailable - boolean, true if Tor is available, false otherwise
+ * useTorMode - boolean, true if dirManager is available and configured to use Tor, false otherwise
+ * socks - object with
+ * command - the name of the gpg executable
+ * args - proxy host URI
+ * envVars - an empty array
+
+ null if Tor is not available
+
+ * helper - object with
+ * envVars - environment variables, if we need them for the helper
+ * command - the path to the helper executable
+ * args - flags used with the helper, if we do not use environment variables
+
+ If no helper is found, return null
+ */
+
+function torProperties(system) {
+ EnigmailLog.DEBUG("tor.jsm: torProperties()\n");
+
+ const tor = system.findTor();
+
+ if (!meetsOSConstraints()) {
+ EnigmailLog.DEBUG("tor.jsm: this version of curl does not support socks5 proxies \n");
+ return torNotAvailableProperties();
+ }
+
+ if (tor === null) {
+ return torNotAvailableProperties();
+ }
+
+ const helper = system.findTorExecutableHelper(EnigmailVersioning);
+ let socks = null;
+ let useTorMode = false;
+
+ if (usesDirmngr()) {
+ useTorMode = EnigmailGpg.dirmngrConfiguredWithTor();
+ }
+ else {
+ socks = buildSocksProperties(tor);
+ }
+
+ return {
+ isAvailable: true,
+ useTorMode: useTorMode,
+ socks: socks,
+ helper: helper
+ };
+}
+
+var EnigmailTor = {
+ torProperties: function() {
+ return torProperties(systemCaller);
+ },
+ getTorNotAvailableProperties: torNotAvailableProperties,
+ isPreferred: isPreferred,
+ isRequired: isRequired
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/trust.jsm
@@ -0,0 +1,114 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailTrust"];
+
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+
+// trust flags according to GPG documentation:
+// - https://www.gnupg.org/documentation/manuals/gnupg.pdf
+// - sources: doc/DETAILS
+// In the order of trustworthy:
+// ---------------------------------------------------------
+// i = The key is invalid (e.g. due to a missing self-signature)
+// n = The key is not valid / Never trust this key
+// d/D = The key has been disabled
+// r = The key has been revoked
+// e = The key has expired
+// g = group (???)
+// ---------------------------------------------------------
+// ? = INTERNAL VALUE to separate invalid from unknown keys
+// see validKeysForAllRecipients() in enigmailMsgComposeHelper.js
+// ---------------------------------------------------------
+// o = Unknown (this key is new to the system)
+// - = Unknown validity (i.e. no value assigned)
+// q = Undefined validity (Not enough information for calculation)
+// '-' and 'q' may safely be treated as the same value for most purposes
+// ---------------------------------------------------------
+// m = Marginally trusted
+// ---------------------------------------------------------
+// f = Fully trusted / valid key
+// u = Ultimately trusted
+// ---------------------------------------------------------
+const TRUSTLEVELS_SORTED = "indDreg?o-qmfu";
+const TRUSTLEVELS_SORTED_IDX_UNKNOWN = 7; // index of '?'
+
+var EnigmailTrust = {
+ /**
+ * @return - |string| containing the order of trust/validity values
+ */
+ trustLevelsSorted: function() {
+ return TRUSTLEVELS_SORTED;
+ },
+
+ /**
+ * @return - |boolean| whether the flag is invalid (neither unknown nor valid)
+ */
+ isInvalid: function(flag) {
+ return TRUSTLEVELS_SORTED.indexOf(flag) < TRUSTLEVELS_SORTED_IDX_UNKNOWN;
+ },
+
+ /**
+ * return a merged value of trust level "key disabled"
+ *
+ * @keyObj - |object| containing the key data
+ *
+ * @return - |string| containing the trust value or "D" for disabled keys
+ */
+ getTrustCode: function(keyObj) {
+ if (keyObj.keyUseFor.indexOf("D") >= 0) {
+ return "D";
+ }
+ else {
+ return keyObj.keyTrust;
+ }
+ },
+
+ getTrustLabel: function(trustCode) {
+ let keyTrust;
+ switch (trustCode) {
+ case 'q':
+ keyTrust = EnigmailLocale.getString("keyValid.unknown");
+ break;
+ case 'i':
+ keyTrust = EnigmailLocale.getString("keyValid.invalid");
+ break;
+ case 'd':
+ case 'D':
+ keyTrust = EnigmailLocale.getString("keyValid.disabled");
+ break;
+ case 'r':
+ keyTrust = EnigmailLocale.getString("keyValid.revoked");
+ break;
+ case 'e':
+ keyTrust = EnigmailLocale.getString("keyValid.expired");
+ break;
+ case 'n':
+ keyTrust = EnigmailLocale.getString("keyTrust.untrusted");
+ break;
+ case 'm':
+ keyTrust = EnigmailLocale.getString("keyTrust.marginal");
+ break;
+ case 'f':
+ keyTrust = EnigmailLocale.getString("keyTrust.full");
+ break;
+ case 'u':
+ keyTrust = EnigmailLocale.getString("keyTrust.ultimate");
+ break;
+ case 'g':
+ keyTrust = EnigmailLocale.getString("keyTrust.group");
+ break;
+ case '-':
+ keyTrust = "-";
+ break;
+ default:
+ keyTrust = "";
+ }
+ return keyTrust;
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/uris.jsm
@@ -0,0 +1,145 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailURIs"];
+
+
+
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
+
+const messageIdList = {};
+const encryptedUris = [];
+
+var EnigmailURIs = {
+ createMessageURI: function(originalUrl, contentType, contentCharset, contentData, persist) {
+ EnigmailLog.DEBUG("enigmail.js: Enigmail.createMessageURI: " + originalUrl +
+ ", " + contentType + ", " + contentCharset + "\n");
+
+ const messageId = "msg" + Math.floor(Math.random() * 1.0e9);
+
+ messageIdList[messageId] = {
+ originalUrl: originalUrl,
+ contentType: contentType,
+ contentCharset: contentCharset,
+ contentData: contentData,
+ persist: persist
+ };
+
+ return "enigmail:message/" + messageId;
+ },
+
+ deleteMessageURI: function(uri) {
+ EnigmailLog.DEBUG("enigmail.js: Enigmail.deleteMessageURI: " + uri + "\n");
+
+ const messageId = EnigmailData.extractMessageId(uri);
+
+ if (!messageId) {
+ return false;
+ }
+ else {
+ return (delete messageIdList[messageId]);
+ }
+ },
+
+ getMessageURI: function(messageId) {
+ return messageIdList[messageId];
+ },
+
+ /*
+ * remember the fact a URI is encrypted
+ *
+ * @param String msgUri
+ *
+ * @return null
+ */
+ rememberEncryptedUri: function(uri) {
+ EnigmailLog.DEBUG("uris.jsm: rememberEncryptedUri: uri=" + uri + "\n");
+ if (encryptedUris.indexOf(uri) < 0) {
+ encryptedUris.push(uri);
+ }
+ },
+
+ /*
+ * unremember the fact a URI is encrypted
+ *
+ * @param String msgUri
+ *
+ * @return null
+ */
+ forgetEncryptedUri: function(uri) {
+ EnigmailLog.DEBUG("uris.jsm: forgetEncryptedUri: uri=" + uri + "\n");
+ const pos = encryptedUris.indexOf(uri);
+ if (pos >= 0) {
+ encryptedUris.splice(pos, 1);
+ }
+ },
+
+ /*
+ * determine if a URI was remebered as encrypted
+ *
+ * @param String msgUri
+ *
+ * @return: Boolean true if yes, false otherwise
+ */
+ isEncryptedUri: function(uri) {
+ EnigmailLog.DEBUG("uris.jsm: isEncryptedUri: uri=" + uri + "\n");
+ return encryptedUris.indexOf(uri) >= 0;
+ },
+
+
+ /**
+ * Determine message number and folder from mailnews URI
+ *
+ * @param url - nsIURI object
+ *
+ * @return Object:
+ * - msgNum: String - the message number, or "" if no URI Scheme fits
+ * - folder: String - the folder (or newsgroup) name
+ */
+ msgIdentificationFromUrl: function(url) {
+
+ // sample URLs in Thunderbird
+ // Local folder: mailbox:///some/path/to/folder?number=359360
+ // IMAP: imap://user@host:port/fetch>some>path>111
+ // NNTP: news://some.host/some.service.com?group=some.group.name&key=3510
+ // also seen: e.g. mailbox:///som/path/to/folder?number=4455522&part=1.1.2&filename=test.eml
+ // mailbox:///...?number=4455522&part=1.1.2&filename=test.eml&type=application/x-message-display&filename=test.eml
+ // imap://user@host:port>UID>some>path>10?header=filter&emitter=js&examineEncryptedParts=true
+
+ if (!url) return null;
+
+ EnigmailLog.DEBUG("uris.jsm: msgIdentificationFromUrl: url.pathQueryRef=" + ("path" in url ? url.path : url.pathQueryRef) + "\n");
+
+ let msgNum = "";
+ let msgFolder = "";
+
+ let pathQueryRef = ("path" in url ? url.path : url.pathQueryRef);
+
+ if (url.schemeIs("mailbox")) {
+ msgNum = pathQueryRef.replace(/(.*[?&]number=)([0-9]+)([^0-9].*)?/, "$2");
+ msgFolder = pathQueryRef.replace(/\?.*/, "");
+ }
+ else if (url.schemeIs("imap")) {
+ let p = unescape(pathQueryRef);
+ msgNum = p.replace(/(.*>)([0-9]+)([^0-9].*)?/, "$2");
+ msgFolder = p.replace(/\?.*$/, "").replace(/>[^>]+$/, "");
+ }
+ else if (url.schemeIs("news")) {
+ msgNum = pathQueryRef.replace(/(.*[?&]key=)([0-9]+)([^0-9].*)?/, "$2");
+ msgFolder = pathQueryRef.replace(/(.*[?&]group=)([^&]+)(&.*)?/, "$2");
+ }
+
+ EnigmailLog.DEBUG("uris.jsm: msgIdentificationFromUrl: msgNum=" + msgNum + " / folder=" + msgFolder + "\n");
+
+ return {
+ msgNum: msgNum,
+ folder: msgFolder.toLowerCase()
+ };
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/verify.jsm
@@ -0,0 +1,29 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailVerifyAttachment"];
+
+
+
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
+const EnigmailCryptoAPI = ChromeUtils.import("chrome://openpgp/content/modules/cryptoAPI.jsm").EnigmailCryptoAPI;
+
+
+
+var EnigmailVerifyAttachment = {
+ attachment: function(verifyFile, sigFile) {
+ EnigmailLog.DEBUG("verify.jsm: EnigmailVerifyAttachment.attachment:\n");
+
+ const verifyFilePath = EnigmailFiles.getEscapedFilename(EnigmailFiles.getFilePathReadonly(verifyFile.QueryInterface(Ci.nsIFile)));
+ const sigFilePath = EnigmailFiles.getEscapedFilename(EnigmailFiles.getFilePathReadonly(sigFile.QueryInterface(Ci.nsIFile)));
+ const cApi = EnigmailCryptoAPI();
+ return cApi.verifyAttachment(verifyFilePath, sigFilePath);
+
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/versioning.jsm
@@ -0,0 +1,128 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+const EXPORTED_SYMBOLS = ["EnigmailVersioning"];
+
+
+
+
+
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailExecution = ChromeUtils.import("chrome://openpgp/content/modules/execution.jsm").EnigmailExecution;
+
+let vc = null;
+
+function getVersionComparator() {
+ if (vc === null) {
+ vc = Cc["@mozilla.org/xpcom/version-comparator;1"].getService(Ci.nsIVersionComparator);
+ }
+ return vc;
+}
+
+/*
+ * getVersion retrieves a version from a string
+ *
+ * @param String output - string to retrieve the version from
+ * @param String executable - string to print when a version is not parseable
+ *
+ * @return String versionResponse - The first value that matches a version format
+ */
+function getVersion(output, executable) {
+ const m = output.match(/\b(\d+\.\d+\.\d+)\b/);
+ if (m) {
+ const versionResponse = m[1];
+
+ EnigmailLog.DEBUG(executable + " version found: " + versionResponse + "\n");
+
+ return versionResponse;
+ }
+ else {
+ return null;
+ }
+}
+
+/**
+ * Test the version number of any application (not gpg)
+ */
+function versionFoundMeetsMinimumVersionRequired(executable, minimumVersion) {
+ const args = ["--version"];
+ const exitCodeObj = {
+ value: null
+ };
+ const output = EnigmailExecution.resolveAndSimpleExec(executable, args, exitCodeObj, {});
+ if (!output || exitCodeObj.value < 0) {
+ EnigmailLog.DEBUG("executable not found: " + executable + "\n");
+ return false;
+ }
+
+ const version = getVersion(output, executable);
+ if (!version) {
+ EnigmailLog.DEBUG("couldn't find a version in the output from " + executable + " - total output: " + output + "\n");
+ return false;
+ }
+
+ return greaterThanOrEqual(version, minimumVersion);
+}
+
+function greaterThanOrEqual(versionWeHave, versionWeAreComparingWith) {
+ return getVersionComparator().compare(versionWeHave, versionWeAreComparingWith) >= 0;
+}
+
+function greaterThan(versionWeHave, versionWeAreComparingWith) {
+ return getVersionComparator().compare(versionWeHave, versionWeAreComparingWith) > 0;
+}
+
+function lessThan(versionWeHave, versionWeAreComparingWith) {
+ return getVersionComparator().compare(versionWeHave, versionWeAreComparingWith) < 0;
+}
+
+var EnigmailVersioning = {
+ /**
+ * Uses Mozilla's Version Comparator Component to identify whether the version
+ * we have is greater than or equal to the version we are comparing with
+ *
+ * @param String versionWeHave - version we have
+ * @param String versionWeAreComparingWith - version we want to compare with
+ *
+ * @return Boolean - The result of versionWeHave >= versionWeAreComparingWith
+ */
+ greaterThanOrEqual: greaterThanOrEqual,
+ /**
+ * Uses Mozilla's Version Comparator Component to identify whether the version
+ * we have is greater than the version we are comparing with
+ *
+ * @param String versionWeHave - version we have
+ * @param String versionWeAreComparingWith - version we want to compare with
+ *
+ * @return Boolean - The result of versionWeHave > versionWeAreComparingWith
+ */
+ greaterThan: greaterThan,
+ /**
+ * Uses Mozilla's Version Comparator Component to identify whether the version
+ * we have is less than the version we are comparing with
+ *
+ * @param String versionWeHave - version we have
+ * @param String versionWeAreComparingWith - version we want to compare with
+ *
+ * @return Boolean - The result of versionWeHave < versionWeAreComparingWith
+ */
+ lessThan: lessThan,
+ /**
+ * Uses Mozilla's Version Comparator Component to identify whether an executable version
+ * meets the required version specified
+ *
+ * @param String executable - version of the executable
+ * @param String minimumVersion - version we want to compare with
+ *
+ * @return Boolean - True if the executable version meets the minimum version required,
+ * false if it does not or it does not exist, or if a version was not
+ * parseable from its output
+ */
+ versionFoundMeetsMinimumVersionRequired: versionFoundMeetsMinimumVersionRequired
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/webKey.jsm
@@ -0,0 +1,437 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/* eslint no-invalid-this: 0 */
+
+/**
+ * This module serves to integrate WKS (Webkey service) into Enigmail
+ */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailWks"];
+
+
+
+
+
+const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
+const EnigmailExecution = ChromeUtils.import("chrome://openpgp/content/modules/execution.jsm").EnigmailExecution;
+const EnigmailGpgAgent = ChromeUtils.import("chrome://openpgp/content/modules/gpgAgent.jsm").EnigmailGpgAgent;
+const EnigmailStdlib = ChromeUtils.import("chrome://openpgp/content/modules/stdlib.jsm").EnigmailStdlib;
+const EnigmailSend = ChromeUtils.import("chrome://openpgp/content/modules/send.jsm").EnigmailSend;
+const EnigmailMimeEncrypt = ChromeUtils.import("chrome://openpgp/content/modules/mimeEncrypt.jsm").EnigmailMimeEncrypt;
+const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
+const EnigmailFuncs = ChromeUtils.import("chrome://openpgp/content/modules/funcs.jsm").EnigmailFuncs;
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+
+const GPG_WKS_CLIENT = "gpg-wks-client";
+
+var EnigmailWks = {
+ wksClientPath: null,
+
+ /**
+ * Get WKS Client path (gpg-wks-client)
+ *
+ * @param window : Object - parent window for dialog display
+ * @param cb : Function(retValue) - callback function.
+ * retValue: nsIFile Object to gpg-wks-client executable or NULL
+ * @return : Object - NULL or a process handle
+ */
+ getWksClientPathAsync: function(window, cb) {
+ EnigmailLog.DEBUG("webKey.jsm: getWksClientPathAsync\n");
+
+ if (EnigmailWks.wksClientPath === null) {
+ let listener = EnigmailExecution.newSimpleListener(null, function(ret) {
+ if (ret === 0) {
+ try {
+ let stdout = listener.stdoutData;
+
+ let libexecdir = /^libexecdir:(.+?)$/m.exec(stdout)[1];
+ if (libexecdir) {
+ libexecdir = libexecdir.replace(/%3a/gi, ":");
+ }
+ else {
+ libexecdir = "";
+ }
+
+ let wks_client = checkIfExists(libexecdir, GPG_WKS_CLIENT);
+ if (!wks_client) {
+ let bindir = /^bindir:(.+?)$/m.exec(stdout)[1];
+ if (bindir) {
+ bindir = bindir.replace(/%3a/gi, ":");
+ }
+ else {
+ bindir = "";
+ }
+ wks_client = checkIfExists(bindir, GPG_WKS_CLIENT);
+
+ if (!wks_client) {
+ cb(null);
+ return;
+ }
+ }
+
+ EnigmailWks.wksClientPath = wks_client;
+ cb(wks_client);
+ }
+ catch (e) {
+ EnigmailLog.DEBUG("webKey.jsm: getWksClientPathAsync: " + e.toString() + "\n");
+ cb(null);
+ }
+ }
+ else {
+ cb(null);
+ }
+ });
+
+ return EnigmailExecution.execStart(EnigmailGpgAgent.gpgconfPath, ["--list-dirs"], false, window, listener, {
+ value: null
+ });
+ }
+ else {
+ cb(EnigmailWks.wksClientPath);
+ return null;
+ }
+ },
+
+ /**
+ * Determine if WKS is supported by email provider
+ *
+ * @param email : String - user's email address
+ * @param window: Object - parent window of dialog display
+ * @param cb : Function(retValue) - callback function.
+ * retValue: Boolean: true if WKS is supported / false otherwise
+ * @return : Object - process handle
+ */
+ isWksSupportedAsync: function(email, window, cb) {
+ EnigmailLog.DEBUG("webKey.jsm: isWksSupportedAsync: email = " + email + "\n");
+ return EnigmailWks.getWksClientPathAsync(window, function(wks_client) {
+ if (wks_client === null) {
+ cb(false);
+ }
+ let listener = EnigmailExecution.newSimpleListener(null, function(ret) {
+ cb(ret === 0);
+ });
+ let proc = EnigmailExecution.execStart(wks_client, ["--supported", email], false, window, listener, {
+ value: null
+ });
+ if (proc === null) {
+ cb(false);
+ }
+ });
+ },
+
+
+ /**
+ * Submit a set of keys to the Web Key Server (WKD)
+ *
+ * @param keys: Array of KeyObj
+ * @param win: parent Window for displaying dialogs
+ * @param observer: Object (KeySrvListener API)
+ * Object implementing:
+ * - onProgress: function(percentComplete) [only implemented for download()]
+ * - onCancel: function() - the body will be set by the callee
+ *
+ * @return Promise<...>
+ */
+ wksUpload: function(keys, win, observer = null) {
+ EnigmailLog.DEBUG(`webKey.jsm: wksUpload(): keys = ${keys.length}\n`);
+ let ids = getWkdIdentities(keys);
+
+ if (observer === null) {
+ observer = {
+ onProgress: function() {}
+ };
+ }
+
+ observer.isCanceled = false;
+ observer.onCancel = function() {
+ this.isCanceled = true;
+ };
+
+ if (!ids) throw "error";
+
+ if (ids.senderIdentities.length === 0) {
+ return new Promise(resolve => {
+ resolve([]);
+ });
+ }
+
+ return performWkdUpload(ids.senderIdentities, win, observer);
+ },
+
+ /**
+ * Submit a key to the email provider (= send publication request)
+ *
+ * @param ident : nsIMsgIdentity - user's ID
+ * @param key : Enigmail KeyObject of user's key
+ * @param window: Object - parent window of dialog display
+ * @param cb : Function(retValue) - callback function.
+ * retValue: Boolean: true if submit was successful / false otherwise
+ * @return : Object - process handle
+ */
+
+ submitKey: function(ident, key, window, cb) {
+ EnigmailLog.DEBUG("webKey.jsm: submitKey(): email = " + ident.email + "\n");
+ return EnigmailWks.getWksClientPathAsync(window, function(wks_client) {
+ if (wks_client === null) {
+ cb(false);
+ return null;
+ }
+ let listener = EnigmailExecution.newSimpleListener(null, function(ret) {
+ if (ret !== 0) {
+ cb(false);
+ return;
+ }
+ EnigmailLog.DEBUG("webKey.jsm: submitKey: send " + listener.stdoutData + "\n");
+ let si = EnigmailMimeEncrypt.createMimeEncrypt(null);
+ let subject = listener.stdoutData.match(/^Subject:[ \t]*(.+)$/im);
+ let to = listener.stdoutData.match(/^To:[ \t]*(.+)$/im);
+
+ if (subject !== null && to !== null) {
+ si.sendFlags = EnigmailConstants.SEND_VERBATIM;
+
+ if (!EnigmailSend.simpleSendMessage({
+ urls: [],
+ identity: ident,
+ to: to[1],
+ subject: subject[1],
+ composeSecure: si
+ },
+ listener.stdoutData,
+ cb
+ )) {
+ cb(false);
+ }
+ }
+ else {
+ cb(false);
+ }
+ });
+ return EnigmailExecution.execStart(wks_client, ["--create", key.fpr, ident.email], false, window, listener, {
+ value: null
+ });
+ });
+ },
+
+ /**
+ * Submit a key to the email provider (= send publication request)
+ *
+ * @param ident : nsIMsgIdentity - user's ID
+ * @param body : String - complete message source of the confirmation-request email obtained
+ * from the email provider
+ * @param window: Object - parent window of dialog display
+ * @param cb : Function(retValue) - callback function.
+ * retValue: Boolean: true if submit was successful / false otherwise
+ * @return : Object - process handle
+ */
+
+ confirmKey: function(ident, body, window, cb) {
+ EnigmailLog.DEBUG("webKey.jsm: confirmKey: ident=" + ident.email + "\n");
+
+ var sanitized = body.replace(/\r?\n/g, "\r\n");
+ return EnigmailWks.getWksClientPathAsync(window, function(wks_client) {
+ if (wks_client === null) {
+ if (cb) {
+ cb(false);
+ }
+ return;
+ }
+ let listener = EnigmailExecution.newSimpleListener(function(pipe) {
+ try {
+ pipe.write(sanitized);
+ pipe.close();
+ }
+ catch (e) {
+ if (cb) {
+ cb(false);
+ }
+ EnigmailLog.DEBUG(e + "\n");
+ }
+ }, function(ret) {
+ try {
+ let si = EnigmailMimeEncrypt.createMimeEncrypt(null);
+ let subject = listener.stdoutData.match(/^Subject:[ \t]*(.+)$/im);
+ let to = listener.stdoutData.match(/^To:[ \t]*(.+)$/im);
+
+ if (subject !== null && to !== null) {
+ si.sendFlags = EnigmailConstants.SEND_VERBATIM;
+
+ if (!EnigmailSend.simpleSendMessage({
+ urls: [],
+ identity: ident,
+ to: to[1],
+ subject: subject[1],
+ composeSecure: si
+ },
+ listener.stdoutData,
+ cb
+ )) {
+ cb(false);
+ }
+ }
+ }
+ catch (e) {
+ if (cb) {
+ cb(false);
+ }
+ EnigmailLog.DEBUG(e + "\n");
+ }
+ });
+ EnigmailExecution.execStart(wks_client, ["--receive"], false, window, listener, {
+ value: null
+ });
+ });
+ }
+};
+
+/**
+ * Check if a file exists and is executable
+ *
+ * @param path: String - directory name
+ * @param execFileName: String - executable name
+ *
+ * @return Object - nsIFile if file exists; NULL otherwise
+ */
+
+function checkIfExists(path, execFileName) {
+ EnigmailLog.DEBUG("webKey.jsm checkIfExists() path=" + path + " execFileName=" + execFileName + "\n");
+
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+
+ execFileName = EnigmailFiles.potentialWindowsExecutable(execFileName);
+ EnigmailFiles.initPath(file, path);
+ file.append(execFileName);
+ if (file.exists() && file.isExecutable()) {
+ return file;
+ }
+ else {
+ return null;
+ }
+}
+
+
+function getWkdIdentities(keys) {
+ EnigmailLog.DEBUG(`webKey.jsm: getWkdIdentities(): keys = ${keys.length}\n`);
+ let senderIdentities = [],
+ notFound = [];
+
+ for (let key of keys) {
+ try {
+ let found = false;
+ for (let uid of key.userIds) {
+ let email = EnigmailFuncs.stripEmail(uid.userId);
+ let maybeIdent = EnigmailStdlib.getIdentityForEmail(email);
+
+ if (maybeIdent && maybeIdent.identity) {
+ senderIdentities.push({
+ identity: maybeIdent.identity,
+ fpr: key.fpr
+ });
+ }
+ }
+ if (!found) {
+ notFound.push(key);
+ }
+ }
+ catch (ex) {
+ EnigmailLog.DEBUG(ex + "\n");
+ return null;
+ }
+ }
+
+ return {
+ senderIdentities: senderIdentities,
+ notFound
+ };
+}
+
+/**
+ * Do the WKD upload and interact with a progress receiver
+ *
+ * @param keyList: Object:
+ * - fprList (String - fingerprint)
+ * - senderIdentities (nsIMsgIdentity)
+ * @param win: nsIWindow - parent window
+ * @param observer: Object:
+ * - onProgress: function(percentComplete [0 .. 100])
+ * called after processing of every key (indpendent of status)
+ * - onUpload: function(fpr)
+ * called after successful uploading of a key
+ * - onFinished: function(completionStatus, errorMessage, displayError)
+ * - isCanceled: Boolean - used to determine if process is canceled
+ */
+function performWkdUpload(keyList, win, observer) {
+ EnigmailLog.DEBUG(`webKey.jsm: performWkdUpload: keyList.length=${keyList.length}\n`);
+
+ let uploads = [];
+
+ let numKeys = keyList.length;
+
+ // For each key fpr/sender identity pair, check whenever WKS is supported
+ // Result is an array of booleans
+ for (let i = 0; i < numKeys; i++) {
+ let keyFpr = keyList[i].fpr;
+ let senderIdent = keyList[i].identity;
+
+ let was_uploaded = new Promise(function _isSupported(resolve, reject) {
+ EnigmailLog.DEBUG("webKey.jsm: performWkdUpload: _isSupported(): ident=" + senderIdent.email + ", key=" + keyFpr + "\n");
+ EnigmailWks.isWksSupportedAsync(senderIdent.email, win, function(is_supported) {
+ if (observer.isCanceled) {
+ EnigmailLog.DEBUG("webKey.jsm: performWkdUpload: canceled by user\n");
+ reject("canceled");
+ }
+
+ EnigmailLog.DEBUG("webKey.jsm: performWkdUpload: ident=" + senderIdent.email + ", supported=" + is_supported + "\n");
+ resolve(is_supported);
+ });
+ }).then(function _submitKey(is_supported) {
+ EnigmailLog.DEBUG(`webKey.jsm: performWkdUpload: _submitKey ${is_supported}\n`);
+ if (is_supported) {
+
+ return new Promise(function(resolve, reject) {
+ EnigmailWks.submitKey(senderIdent, {
+ 'fpr': keyFpr
+ }, win, function(success) {
+ observer.onProgress((i + 1) / numKeys * 100);
+ if (success) {
+ resolve(senderIdent);
+ }
+ else {
+ reject("submitFailed");
+ }
+ });
+ });
+ }
+ else {
+ observer.onProgress((i + 1) / numKeys * 100);
+ return Promise.resolve(null);
+ }
+ });
+
+ uploads.push(was_uploaded);
+ }
+
+ return Promise.all(uploads).catch(function(reason) {
+ //let errorMsg = EnigmailLocale.getString("keyserverProgress.wksUploadFailed");
+ return [];
+ }).then(function(senders) {
+ let uploaded_uids = [];
+ if (senders) {
+ senders.forEach(function(val) {
+ if (val !== null) {
+ uploaded_uids.push(val.email);
+ }
+ });
+ }
+ observer.onProgress(100);
+
+ return uploaded_uids;
+ });
+}
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/windows.jsm
@@ -0,0 +1,652 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailWindows"];
+
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+const EnigmailKeyRing = ChromeUtils.import("chrome://openpgp/content/modules/keyRing.jsm").EnigmailKeyRing;
+const EnigmailRules = ChromeUtils.import("chrome://openpgp/content/modules/rules.jsm").EnigmailRules;
+const EnigmailApp = ChromeUtils.import("chrome://openpgp/content/modules/app.jsm").EnigmailApp;
+const EnigmailCompat = ChromeUtils.import("chrome://openpgp/content/modules/compat.jsm").EnigmailCompat;
+const EnigmailStdlib = ChromeUtils.import("chrome://openpgp/content/modules/stdlib.jsm").EnigmailStdlib;
+
+const APPSHELL_MEDIATOR_CONTRACTID = "@mozilla.org/appshell/window-mediator;1";
+const APPSHSVC_CONTRACTID = "@mozilla.org/appshell/appShellService;1";
+
+const LOCAL_FILE_CONTRACTID = "@mozilla.org/file/local;1";
+const IOSERVICE_CONTRACTID = "@mozilla.org/network/io-service;1";
+
+var EnigmailWindows = {
+ /**
+ * Display the OpenPGP setup wizard window
+ *
+ * win : nsIWindow - the parent window
+ * skipIntro: Boolean - optional, if true, skip the introduction page
+ *
+ * no return value
+ */
+ openSetupWizard: function(win, setupType) {
+ EnigmailLog.DEBUG("windows.jsm: openSetupWizard()\n");
+
+ if (!EnigmailStdlib.hasConfiguredAccounts()) {
+ EnigmailLog.DEBUG("windows.jsm: openSetupWizard: no configured accounts\n");
+ return;
+ }
+
+ win.open("chrome://openpgp/content/ui/setupWizard2.xul",
+ "", "chrome,centerscreen,resizable");
+ },
+
+ /**
+ * Open a window, or focus it if it is already open
+ *
+ * @winName : String - name of the window; used to identify if it is already open
+ * @spec : String - window URL (e.g. chrome://openpgp/content/ui/test.xul)
+ * @winOptions: String - window options as defined in nsIWindow.open
+ * @optObj : any - an Object, Array, String, etc. that is passed as parameter
+ * to the window
+ */
+ openWin: function(winName, spec, winOptions, optObj) {
+ var windowManager = Cc[APPSHELL_MEDIATOR_CONTRACTID].getService(Ci.nsIWindowMediator);
+
+ var winEnum = windowManager.getEnumerator(null);
+ var recentWin = null;
+ while (winEnum.hasMoreElements() && !recentWin) {
+ var thisWin = winEnum.getNext();
+ if (thisWin.location.href == spec) {
+ recentWin = thisWin;
+ break;
+ }
+ if (winName && thisWin.name && thisWin.name == winName) {
+ thisWin.focus();
+ break;
+ }
+
+ }
+
+ if (recentWin) {
+ recentWin.focus();
+ }
+ else {
+ var appShellSvc = Cc[APPSHSVC_CONTRACTID].getService(Ci.nsIAppShellService);
+ var domWin = appShellSvc.hiddenDOMWindow;
+ try {
+ domWin.open(spec, winName, "chrome," + winOptions, optObj);
+ }
+ catch (ex) {
+ domWin = windowManager.getMostRecentWindow(null);
+ domWin.open(spec, winName, "chrome," + winOptions, optObj);
+ }
+ }
+ },
+
+ /**
+ * Determine the best possible window to serve as parent window for dialogs.
+ *
+ * @return: nsIWindow object
+ */
+ getBestParentWin: function() {
+ var windowManager = Cc[APPSHELL_MEDIATOR_CONTRACTID].getService(Ci.nsIWindowMediator);
+
+ var bestFit = null;
+ var winEnum = windowManager.getEnumerator(null);
+
+ while (winEnum.hasMoreElements()) {
+ var thisWin = winEnum.getNext();
+ if (thisWin.location.href.search(/\/messenger.xul$/) > 0) {
+ bestFit = thisWin;
+ }
+ if (!bestFit && thisWin.location.href.search(/\/messengercompose.xul$/) > 0) {
+ bestFit = thisWin;
+ }
+ }
+
+ if (!bestFit) {
+ winEnum = windowManager.getEnumerator(null);
+ bestFit = winEnum.getNext();
+ }
+
+ return bestFit;
+ },
+
+ /**
+ * Iterate through the frames of a window and return the first frame with a
+ * matching name.
+ *
+ * @win: nsIWindow - XUL window to search
+ * @frameName: String - name of the frame to seach
+ *
+ * @return: the frame object or null if not found
+ */
+ getFrame: function(win, frameName) {
+ EnigmailLog.DEBUG("windows.jsm: getFrame: name=" + frameName + "\n");
+ for (var j = 0; j < win.frames.length; j++) {
+ if (win.frames[j].name == frameName) {
+ return win.frames[j];
+ }
+ }
+ return null;
+ },
+
+ getMostRecentWindow: function() {
+ var windowManager = Cc[APPSHELL_MEDIATOR_CONTRACTID].getService(Ci.nsIWindowMediator);
+ return windowManager.getMostRecentWindow(null);
+ },
+
+
+ /**
+ * Display the key help window
+ *
+ * @source - |string| containing the name of the file to display
+ *
+ * no return value
+ */
+
+ openHelpWindow: function(source) {
+ EnigmailWindows.openWin("enigmail:help",
+ "chrome://openpgp/content/ui/enigmailHelp.xul?src=" + source,
+ "centerscreen,resizable");
+ },
+
+ /**
+ * Display the "About Enigmail" window
+ *
+ * no return value
+ */
+ openAboutWindow: function() {
+ EnigmailWindows.openMailTab("chrome://openpgp/content/ui/aboutEnigmail.html");
+ },
+
+ /**
+ * Open the Enigmail Documentation page in a new window
+ *
+ * no return value
+ */
+ openEnigmailDocu: function(parent) {
+ if (!parent) {
+ parent = this.getMostRecentWindow();
+ }
+
+ parent.open("https://doesnotexist-openpgp-integration.thunderbird/faq/docu.php", "", "chrome,width=600,height=500,resizable");
+ },
+
+ /**
+ * Display the Per-Recipient Rules editor window
+ *
+ * no return value
+ */
+ openRulesEditor: function() {
+ EnigmailWindows.openWin("enigmail:rulesEditor",
+ "chrome://openpgp/content/ui/enigmailRulesEditor.xul",
+ "dialog,centerscreen,resizable");
+ },
+
+ /**
+ * Display the OpenPGP key manager window
+ *
+ * no return value
+ */
+ openKeyManager: function(win) {
+ EnigmailCore.getService(win);
+
+ EnigmailWindows.openWin("enigmail:KeyManager",
+ "chrome://openpgp/content/ui/enigmailKeyManager.xul",
+ "resizable");
+ },
+
+ /**
+ * Display the OpenPGP key manager window
+ *
+ * no return value
+ */
+ openImportSettings: function(win) {
+ EnigmailCore.getService(win);
+
+ EnigmailWindows.openWin("",
+ "chrome://openpgp/content/ui/importSettings.xul",
+ "chrome,dialog,centerscreen,resizable,modal");
+ },
+
+ /**
+ * If the Key Manager is open, dispatch an event to tell the key
+ * manager to refresh the displayed keys
+ */
+
+ keyManReloadKeys: function() {
+ let windowManager = Cc[APPSHELL_MEDIATOR_CONTRACTID].getService(Ci.nsIWindowMediator);
+ const winName = "enigmail:KeyManager";
+ const spec = "chrome://openpgp/content/ui/enigmailKeygen.xul";
+
+ let winEnum = windowManager.getEnumerator(null);
+ let recentWin = null;
+ while (winEnum.hasMoreElements() && !recentWin) {
+ let thisWin = winEnum.getNext();
+ if (thisWin.location.href == spec) {
+ recentWin = thisWin;
+ break;
+ }
+ if (thisWin.name && thisWin.name == winName) {
+ let evt = new thisWin.Event("reload-keycache", {
+ "bubbles": true,
+ "cancelable": false
+ });
+ thisWin.dispatchEvent(evt);
+ break;
+ }
+ }
+ },
+
+ /**
+ * Display the key creation window
+ *
+ * no return value
+ */
+ openKeyGen: function() {
+ EnigmailWindows.openWin("enigmail:generateKey",
+ "chrome://openpgp/content/ui/enigmailKeygen.xul",
+ "chrome,resizable=yes");
+ },
+
+ /**
+ * Display the card details window
+ *
+ * no return value
+ */
+ openCardDetails: function() {
+ EnigmailWindows.openWin("enigmail:cardDetails",
+ "chrome://openpgp/content/ui/enigmailCardDetails.xul",
+ "centerscreen");
+ },
+
+
+ /**
+ * Display the console log window
+ *
+ * @win - |object| holding the parent window for the dialog
+ *
+ * no return value
+ */
+ openConsoleWindow: function() {
+ EnigmailWindows.openWin("enigmail:console",
+ "chrome://openpgp/content/ui/enigmailConsole.xul",
+ "resizable,centerscreen");
+ },
+
+ /**
+ * Display the window for the debug log file
+ *
+ * @win - |object| holding the parent window for the dialog
+ *
+ * no return value
+ */
+ openDebugLog: function(win) {
+ EnigmailWindows.openWin("enigmail:logFile",
+ "chrome://openpgp/content/ui/enigmailViewFile.xul?viewLog=1&title=" + escape(EnigmailLocale.getString("debugLog.title")),
+ "centerscreen");
+ },
+
+ /**
+ * Display the preferences dialog
+ *
+ * @win - |object| holding the parent window for the dialog
+ * @showBasic - |boolean| true if only the 1st page of the preferences window
+ * should be displayed / false otherwise
+ * @selectTab - |string| ID of the tab element (in XUL) to display when opening
+ *
+ * no return value
+ */
+ openPrefWindow: function(win, showBasic, selectTab) {
+ EnigmailLog.DEBUG("windows.js: openPrefWindow\n");
+
+ EnigmailCore.getService(win, true); // true: starting preferences dialog
+
+ let url;
+
+ url = "chrome://openpgp/content/ui/pref-enigmail.xul";
+ win.openDialog(url,
+ "_blank", "chrome,resizable=yes", {
+ 'showBasic': showBasic,
+ 'clientType': 'thunderbird',
+ 'selectTab': selectTab
+ });
+ },
+
+ /**
+ * Display the dialog for creating a new per-recipient rule
+ *
+ * @win - |object| holding the parent window for the dialog
+ * @emailAddress - |string| containing the email address for the rule
+ *
+ * @return - always true
+ */
+ createNewRule: function(win, emailAddress) {
+ // make sure the rules database is loaded
+ const enigmailSvc = EnigmailCore.getService(win);
+ if (!enigmailSvc) {
+ return false;
+ }
+
+ // open rule dialog
+ EnigmailRules.getRulesData({});
+
+ const inputObj = {
+ toAddress: "{" + emailAddress + "}",
+ options: "",
+ command: "add"
+ };
+ win.openDialog("chrome://openpgp/content/ui/enigmailSingleRcptSettings.xul", "",
+ "dialog,modal,centerscreen,resizable", inputObj, {});
+ return true;
+ },
+
+ /**
+ * Display the dialog for changing the expiry date of one or several keys
+ *
+ * @win - |object| holding the parent window for the dialog
+ * @userIdArr - |array| of |strings| containing the User IDs
+ * @keyIdArr - |array| of |strings| containing the key IDs (eg. "0x12345678") to change
+ *
+ * @return Boolean - true if expiry date was changed; false otherwise
+ */
+ editKeyExpiry: function(win, userIdArr, keyIdArr) {
+ const inputObj = {
+ keyId: keyIdArr,
+ userId: userIdArr
+ };
+ const resultObj = {
+ refresh: false
+ };
+ win.openDialog("chrome://openpgp/content/ui/enigmailEditKeyExpiryDlg.xul", "",
+ "dialog,modal,centerscreen,resizable", inputObj, resultObj);
+ return resultObj.refresh;
+ },
+
+ /**
+ * Display the dialog for changing key trust of one or several keys
+ *
+ * @win - |object| holding the parent window for the dialog
+ * @userIdArr - |array| of |strings| containing the User IDs
+ * @keyIdArr - |array| of |strings| containing the key IDs (eg. "0x12345678") to change
+ *
+ * @return Boolean - true if key trust was changed; false otherwise
+ */
+ editKeyTrust: function(win, userIdArr, keyIdArr) {
+ const inputObj = {
+ keyId: keyIdArr,
+ userId: userIdArr
+ };
+ const resultObj = {
+ refresh: false
+ };
+ win.openDialog("chrome://openpgp/content/ui/enigmailEditKeyTrustDlg.xul", "",
+ "dialog,modal,centerscreen,resizable", inputObj, resultObj);
+ return resultObj.refresh;
+ },
+
+
+ /**
+ * Display the dialog for signing a key
+ *
+ * @win - |object| holding the parent window for the dialog
+ * @userId - |string| containing the User ID (for displaing in the dialog only)
+ * @keyId - |string| containing the key ID (eg. "0x12345678")
+ *
+ * @return Boolean - true if key was signed; false otherwise
+ */
+ signKey: function(win, userId, keyId) {
+ const inputObj = {
+ keyId: keyId,
+ userId: userId
+ };
+ const resultObj = {
+ refresh: false
+ };
+ win.openDialog("chrome://openpgp/content/ui/enigmailSignKeyDlg.xul", "",
+ "dialog,modal,centerscreen,resizable", inputObj, resultObj);
+ return resultObj.refresh;
+ },
+
+ /**
+ * Display the photo ID associated with a key
+ *
+ * @win - |object| holding the parent window for the dialog
+ * @keyId - |string| containing the key ID (eg. "0x12345678")
+ * @userId - |string| containing the User ID (for displaing in the dialog only)
+ * @photoNumber - |number| UAT entry in the squence of appearance in the key listing, starting with 0
+ * no return value
+ */
+ showPhoto: function(win, keyId, userId, photoNumber) {
+ const enigmailSvc = EnigmailCore.getService(win);
+ if (enigmailSvc) {
+ if (!photoNumber) photoNumber = 0;
+ let keyObj = EnigmailKeyRing.getKeyById(keyId);
+ if (!keyObj) {
+ EnigmailWindows.alert(win, EnigmailLocale.getString("noPhotoAvailable"));
+ }
+
+ let photoFile = keyObj.getPhotoFile(photoNumber);
+
+ if (photoFile) {
+ if (!(photoFile.isFile() && photoFile.isReadable())) {
+ EnigmailWindows.alert(win, EnigmailLocale.getString("error.photoPathNotReadable", photoFile.path));
+ }
+ else {
+ const photoUri = Cc[IOSERVICE_CONTRACTID].getService(Ci.nsIIOService).
+ newFileURI(photoFile).spec;
+ const argsObj = {
+ photoUri: photoUri,
+ userId: userId,
+ keyId: keyId
+ };
+
+ win.openDialog("chrome://openpgp/content/ui/enigmailDispPhoto.xul",
+ photoUri,
+ "chrome,modal,resizable,dialog,centerscreen",
+ argsObj);
+ try {
+ // delete the photo file
+ photoFile.remove(false);
+ }
+ catch (ex) {}
+ }
+ }
+ else {
+ EnigmailWindows.alert(win, EnigmailLocale.getString("noPhotoAvailable"));
+ }
+ }
+ },
+
+ /**
+ * Display the OpenPGP Key Details window
+ *
+ * @win - |object| holding the parent window for the dialog
+ * @keyId - |string| containing the key ID (eg. "0x12345678")
+ * @refresh - |boolean| if true, cache is cleared and the key data is loaded from GnuPG
+ *
+ * @return Boolean - true: keylist needs to be refreshed
+ * - false: no need to refresh keylist
+ */
+ openKeyDetails: function(win, keyId, refresh) {
+ const keyListObj = {};
+
+ if (!win) {
+ win = this.getBestParentWin();
+ }
+
+ keyId = keyId.replace(/^0x/, "");
+
+ if (refresh) {
+ EnigmailKeyRing.clearCache();
+ }
+
+ const inputObj = {
+ keyId: keyId
+ };
+ const resultObj = {
+ refresh: false
+ };
+ win.openDialog("chrome://openpgp/content/ui/keyDetailsDlg.xul", "",
+ "dialog,modal,centerscreen,resizable", inputObj, resultObj);
+ if (resultObj.refresh) {
+ EnigmailKeyRing.clearCache();
+ }
+
+ return resultObj.refresh;
+ },
+
+ /**
+ * Display the dialog to search and/or download key(s) from a keyserver
+ *
+ * @win - |object| holding the parent window for the dialog
+ * @inputObj - |object| with member searchList (|string| containing the keys to search)
+ * @resultObj - |object| with member importedKeys (|number| containing the number of imporeted keys)
+ *
+ * no return value
+ */
+ downloadKeys: function(win, inputObj, resultObj) {
+ EnigmailLog.DEBUG("windows.jsm: downloadKeys: searchList=" + inputObj.searchList + "\n");
+
+ resultObj.importedKeys = 0;
+
+ const ioService = Cc[IOSERVICE_CONTRACTID].getService(Ci.nsIIOService);
+ if (ioService && ioService.offline) {
+ EnigmailWindows.alert(win, EnigmailLocale.getString("needOnline"));
+ return;
+ }
+
+ let valueObj = {};
+ if (inputObj.searchList) {
+ valueObj = {
+ keyId: "<" + inputObj.searchList.join("> <") + ">"
+ };
+ }
+
+ const keysrvObj = {};
+
+ if (inputObj.searchList && inputObj.autoKeyServer) {
+ keysrvObj.value = inputObj.autoKeyServer;
+ }
+ else {
+ win.openDialog("chrome://openpgp/content/ui/enigmailKeyserverDlg.xul",
+ "", "dialog,modal,centerscreen", valueObj, keysrvObj);
+ }
+
+ if (!keysrvObj.value) {
+ return;
+ }
+
+ inputObj.keyserver = keysrvObj.value;
+
+ if (!inputObj.searchList) {
+ const searchval = keysrvObj.email.
+ replace(/^(\s*)(.*)/, "$2").
+ replace(/\s+$/, ""); // trim spaces
+ // special handling to convert fingerprints with spaces into fingerprint without spaces
+ if (searchval.length == 49 && searchval.match(/^[0-9a-fA-F ]*$/) &&
+ searchval[4] == ' ' && searchval[9] == ' ' && searchval[14] == ' ' &&
+ searchval[19] == ' ' && searchval[24] == ' ' && searchval[29] == ' ' &&
+ searchval[34] == ' ' && searchval[39] == ' ' && searchval[44] == ' ') {
+ inputObj.searchList = ["0x" + searchval.replace(/ /g, "")];
+ }
+ else if (searchval.length == 40 && searchval.match(/^[0-9a-fA-F ]*$/)) {
+ inputObj.searchList = ["0x" + searchval];
+ }
+ else if (searchval.length == 8 && searchval.match(/^[0-9a-fA-F]*$/)) {
+ // special handling to add the required leading 0x when searching for keys
+ inputObj.searchList = ["0x" + searchval];
+ }
+ else if (searchval.length == 16 && searchval.match(/^[0-9a-fA-F]*$/)) {
+ inputObj.searchList = ["0x" + searchval];
+ }
+ else {
+ inputObj.searchList = searchval.split(/[,; ]+/);
+ }
+ }
+
+ win.openDialog("chrome://openpgp/content/ui/enigmailSearchKey.xul",
+ "", "dialog,modal,centerscreen", inputObj, resultObj);
+ },
+
+ /**
+ * Display Autocrypt Setup Passwd dialog.
+ *
+ * @param dlgMode: String - dialog mode: "input" / "display"
+ * @param passwdType: String - type of password ("numeric9x4" / "generic")
+ * @param password: String - password or initial two digits of password
+ *
+ * @return String entered password (in input mode) or NULL
+ */
+ autocryptSetupPasswd: function(window, dlgMode, passwdType = "numeric9x4", password) {
+ if (!window) {
+ window = this.getBestParentWin();
+ }
+
+ let inputObj = {
+ password: null,
+ passwdType: passwdType,
+ dlgMode: dlgMode
+ };
+
+ if (password) inputObj.initialPasswd = password;
+
+ window.openDialog("chrome://openpgp/content/ui/autocryptSetupPasswd.xul",
+ "", "dialog,modal,centerscreen", inputObj);
+
+ return inputObj.password;
+ },
+
+ /**
+ * Display dialog to initiate the Autocrypt Setup Message.
+ *
+ */
+ inititateAcSetupMessage: function(window) {
+ if (!window) {
+ window = this.getBestParentWin();
+ }
+
+ window.openDialog("chrome://openpgp/content/ui/autocryptInitiateBackup.xul",
+ "", "dialog,centerscreen");
+ },
+
+ /**
+ * Open a URL in a tab on the main window. The URL can either be a web page
+ * (e.g. https://doesnotexist-openpgp-integration.thunderbird/ or a chrome document (e.g. chrome://openpgp/content/ui/x.xul))
+ *
+ * @param aURL: String - the URL to open
+ * @param winName: String - name of the window; used to identify if it is already open
+ */
+ openMailTab: function(aURL, windowName) {
+ let tabs = EnigmailStdlib.getMail3Pane().document.getElementById("tabmail");
+
+ for (let i = 0; i < tabs.tabInfo.length; i++) {
+ if ("openedUrl" in tabs.tabInfo[i] && tabs.tabInfo[i].openedUrl.startsWith(aURL)) {
+ tabs.switchToTab(i);
+ return;
+ }
+ }
+
+ let gotTab = tabs.openTab("chromeTab", {
+ chromePage: aURL
+ });
+ gotTab.openedUrl = aURL;
+ },
+
+ shutdown: function(reason) {
+ EnigmailLog.DEBUG("windows.jsm: shutdown()\n");
+
+ let tabs = EnigmailStdlib.getMail3Pane().document.getElementById("tabmail");
+
+ for (let i = tabs.tabInfo.length - 1; i >= 0; i--) {
+ if ("openedUrl" in tabs.tabInfo[i] && tabs.tabInfo[i].openedUrl.startsWith("chrome://openpgp/")) {
+ tabs.closeTab(tabs.tabInfo[i]);
+ }
+ }
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/wkdLookup.jsm
@@ -0,0 +1,417 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * Lookup keys by email addresses using WKD. A an email address is lookep up at most
+ * once a day. (see https://tools.ietf.org/html/draft-koch-openpgp-webkey-service)
+ */
+
+var EXPORTED_SYMBOLS = ["EnigmailWkdLookup"];
+
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailFuncs = ChromeUtils.import("chrome://openpgp/content/modules/funcs.jsm").EnigmailFuncs;
+const PromiseUtils = ChromeUtils.import("resource://gre/modules/PromiseUtils.jsm").PromiseUtils;
+const EnigmailKeyRing = ChromeUtils.import("chrome://openpgp/content/modules/keyRing.jsm").EnigmailKeyRing;
+const EnigmailZBase32 = ChromeUtils.import("chrome://openpgp/content/modules/zbase32.jsm").EnigmailZBase32;
+const EnigmailOpenPGP = ChromeUtils.import("chrome://openpgp/content/modules/openpgp.jsm").EnigmailOpenPGP;
+const EnigmailKey = ChromeUtils.import("chrome://openpgp/content/modules/key.jsm").EnigmailKey;
+const EnigmailDns = ChromeUtils.import("chrome://openpgp/content/modules/dns.jsm").EnigmailDns;
+const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
+const EnigmailSqliteDb = ChromeUtils.import("chrome://openpgp/content/modules/sqliteDb.jsm").EnigmailSqliteDb;
+
+Components.utils.importGlobalProperties(["fetch"]);
+
+// Those domains are not expected to have WKD:
+var BLACKLIST_DOMAINS = [
+ /* Default domains included */
+ "aol.com", "att.net", "comcast.net", "facebook.com", "gmail.com", "gmx.com", "googlemail.com",
+ "google.com", "hotmail.com", "hotmail.co.uk", "mac.com", "me.com", "mail.com", "msn.com",
+ "live.com", "sbcglobal.net", "verizon.net", "yahoo.com", "yahoo.co.uk",
+
+ /* Other global domains */
+ "email.com", "games.com" /* AOL */ , "gmx.net", "icloud.com",
+ "iname.com", "inbox.com", "lavabit.com", "love.com" /* AOL */ , "outlook.com", "pobox.com", "tutanota.de", "tutanota.com", "tutamail.com", "tuta.io",
+ "keemail.me", "rocketmail.com" /* Yahoo */ , "safe-mail.net", "wow.com" /* AOL */ , "ygm.com" /* AOL */ ,
+ "ymail.com" /* Yahoo */ , "zoho.com", "yandex.com",
+
+ /* United States ISP domains */
+ "bellsouth.net", "charter.net", "cox.net", "earthlink.net", "juno.com",
+
+ /* British ISP domains */
+ "btinternet.com", "virginmedia.com", "blueyonder.co.uk", "freeserve.co.uk", "live.co.uk",
+ "ntlworld.com", "o2.co.uk", "orange.net", "sky.com", "talktalk.co.uk", "tiscali.co.uk",
+ "virgin.net", "wanadoo.co.uk", "bt.com",
+
+ /* Domains used in Asia */
+ "sina.com", "sina.cn", "qq.com", "naver.com", "hanmail.net", "daum.net", "nate.com", "yahoo.co.jp", "yahoo.co.kr", "yahoo.co.id", "yahoo.co.in", "yahoo.com.sg", "yahoo.com.ph", "163.com", "yeah.net", "126.com", "21cn.com", "aliyun.com", "foxmail.com",
+
+ /* French ISP domains */
+ "hotmail.fr", "live.fr", "laposte.net", "yahoo.fr", "wanadoo.fr", "orange.fr", "gmx.fr", "sfr.fr", "neuf.fr", "free.fr",
+
+ /* German ISP domains */
+ "gmx.de", "hotmail.de", "live.de", "online.de", "t-online.de" /* T-Mobile */ , "web.de", "yahoo.de",
+
+ /* Italian ISP domains */
+ "libero.it", "virgilio.it", "hotmail.it", "aol.it", "tiscali.it", "alice.it", "live.it", "yahoo.it", "email.it", "tin.it", "poste.it", "teletu.it",
+
+ /* Russian ISP domains */
+ "mail.ru", "rambler.ru", "yandex.ru", "ya.ru", "list.ru",
+
+ /* Belgian ISP domains */
+ "hotmail.be", "live.be", "skynet.be", "voo.be", "tvcablenet.be", "telenet.be",
+
+ /* Argentinian ISP domains */
+ "hotmail.com.ar", "live.com.ar", "yahoo.com.ar", "fibertel.com.ar", "speedy.com.ar", "arnet.com.ar",
+
+ /* Domains used in Mexico */
+ "yahoo.com.mx", "live.com.mx", "hotmail.es", "hotmail.com.mx", "prodigy.net.mx",
+
+ /* Domains used in Canada */
+ "yahoo.ca", "hotmail.ca", "bell.net", "shaw.ca", "sympatico.ca", "rogers.com",
+
+ /* Domains used in Brazil */
+ "yahoo.com.br", "hotmail.com.br", "outlook.com.br", "uol.com.br", "bol.com.br", "terra.com.br", "ig.com.br", "itelefonica.com.br", "r7.com", "zipmail.com.br", "globo.com", "globomail.com", "oi.com.br"
+];
+
+var EnigmailWkdLookup = {
+
+ /**
+ * Try to import keys using WKD. Found keys are automatically imported
+ *
+ * @param {Array of String} emailList: email addresses (in lowercase)
+ *
+ * @return {Promise<Boolean>}: true - new keys found
+ */
+ findKeys: function(emails) {
+ return new Promise((resolve, reject) => {
+ EnigmailLog.DEBUG("wkdLookup.jsm: findKeys(" + emails.join(",") + ")\n");
+
+ if (emails.length === 0) {
+ resolve(false);
+ return;
+ }
+
+ let self = this;
+
+ // do a little sanity test such that we don't do the lookup for nothing too often
+ for (let e of emails) {
+ if (e.search(/.@.+\...+$/) < 0) {
+ resolve(false);
+ return;
+ }
+ }
+
+ Promise.all(emails.map(
+ function(mailAddr) {
+ return self.determineLastAttempt(mailAddr.trim().toLowerCase());
+ }))
+ .then(function(checks) {
+ let toCheck = [];
+
+ EnigmailLog.DEBUG("wkdLookup.jsm: findKeys: checks " + checks.length + "\n");
+
+ for (let i = 0; i < checks.length; i++) {
+ if (checks[i]) {
+ EnigmailLog.DEBUG("wkdLookup.jsm: findKeys: recheck " + emails[i] + "\n");
+ toCheck.push(emails[i]);
+ }
+ else {
+ EnigmailLog.DEBUG("wkdLookup.jsm: findKeys: skip check " + emails[i] + "\n");
+ }
+ }
+
+ if (toCheck.length > 0) {
+
+ Promise.all(toCheck.map((email) => {
+ return self.downloadKey(email);
+ })).then((dataArr) => {
+
+ if (dataArr) {
+ let gotKeys = [];
+ for (let i = 0; i < dataArr.length; i++) {
+ if (dataArr[i] !== null) {
+ gotKeys.push(dataArr[i]);
+ }
+ }
+
+ if (gotKeys.length > 0) {
+ importDownloadedKeys(gotKeys);
+ resolve(true);
+ }
+ else
+ resolve(false);
+ }
+ });
+ }
+ else {
+ resolve(false);
+ }
+
+ })
+ .catch(() => {
+ resolve(false);
+ });
+ });
+ },
+
+ /**
+ * Determine for an email address when we last attempted to
+ * obtain a key via wkd
+ *
+ * @param {String} email: email address
+ *
+ * @return {Promise<Boolean>}: true if new WKD lookup required
+ */
+ determineLastAttempt: async function(email) {
+ EnigmailLog.DEBUG("wkdLookup.jsm: determineLastAttempt(" + email + ")\n");
+
+ let conn;
+ try {
+ conn = await EnigmailSqliteDb.openDatabase();
+ let val = await timeForRecheck(conn, email);
+ conn.close();
+ return val;
+ }
+ catch (x) {
+ EnigmailLog.DEBUG("wkdLookup.jsm: determineLastAttempt: could not open database\n");
+ if (conn) {
+ EnigmailLog.DEBUG("wkdLookup.jsm: error - closing connection: " + x + "\n");
+ conn.close();
+ }
+ }
+ // in case something goes wrong we recheck anyway
+ return true;
+ },
+
+ /**
+ * get the download URL for an email address for WKD or domain-specific locations
+ *
+ * @param {String} email: email address
+ *
+ * @return {Promise<String>}: URL (or null if not possible)
+ */
+
+ getDownloadUrlFromEmail: async function(email, advancedMethod) {
+ email = email.toLowerCase().trim();
+
+ let url = await getSiteSpecificUrl(email);
+ if (url) return url;
+
+ let at = email.indexOf("@");
+
+ let domain = email.substr(at + 1);
+ let user = email.substr(0, at);
+
+ var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Ci.nsIScriptableUnicodeConverter);
+ converter.charset = "UTF-8";
+ var data = converter.convertToByteArray(user, {});
+
+ var ch = Components.classes["@mozilla.org/security/hash;1"].createInstance(Components.interfaces.nsICryptoHash);
+ ch.init(ch.SHA1);
+ ch.update(data, data.length);
+ let gotHash = ch.finish(false);
+ let encodedHash = EnigmailZBase32.encode(gotHash);
+
+ if (advancedMethod) {
+ url = "https://openpgpkey." + domain + "/.well-known/openpgpkey/" + domain + "/hu/" + encodedHash + "?l=" + escape(user);
+ }
+ else {
+ url = "https://" + domain + "/.well-known/openpgpkey/hu/" + encodedHash + "?l=" + escape(user);
+ }
+
+ return url;
+ },
+
+ /**
+ * Download a key for an email address
+ *
+ * @param {String} email: email address
+ *
+ * @return {Promise<String>}: Key data (or null if not possible)
+ */
+ downloadKey: async function(email) {
+ EnigmailLog.DEBUG("wkdLookup.jsm: downloadKey(" + email + ")\n");
+
+ if (!this.isWkdAvailable(email)) {
+ EnigmailLog.DEBUG("wkdLookup.jsm: downloadKey: no WKD for the domain\n");
+ return null;
+ }
+
+ let keyData = await this.doWkdKeyDownload(email, true);
+
+ if (!keyData) {
+ keyData = await this.doWkdKeyDownload(email, false);
+ }
+
+ return keyData;
+ },
+
+ doWkdKeyDownload: async function(email, advancedMethod) {
+ EnigmailLog.DEBUG(`wkdLookup.jsm: doWkdKeyDownload(${email}, ${advancedMethod})\n`);
+
+ let url = await EnigmailWkdLookup.getDownloadUrlFromEmail(email, advancedMethod);
+
+ let padLen = (url.length % 512) + 1;
+ let hdrs = new Headers({
+ 'Authorization': 'Basic ' + btoa("no-user:")
+ });
+ hdrs.append('Content-Type', 'application/octet-stream');
+ hdrs.append('X-Enigmail-Padding', "x".padEnd(padLen, "x"));
+
+ let myRequest = new Request(url, {
+ method: 'GET',
+ headers: hdrs,
+ mode: 'cors',
+ //redirect: 'error',
+ redirect: 'follow',
+ cache: 'default'
+ });
+
+ let response;
+ try {
+ EnigmailLog.DEBUG("wkdLookup.jsm: doWkdKeyDownload: requesting " + url + "\n");
+ response = await fetch(myRequest);
+ if (!response.ok) {
+ return null;
+ }
+ }
+ catch (ex) {
+ EnigmailLog.DEBUG("wkdLookup.jsm: doWkdKeyDownload: error " + ex.toString() + "\n");
+ return null;
+ }
+
+ try {
+ if (response.headers.has("content-type") && response.headers.get("content-type").search(/^text\/html/i) === 0) {
+ // if we get HTML output, we return nothing (for example redirects to error catching pages)
+ return null;
+ }
+ let keyData = EnigmailData.arrayBufferToString(Cu.cloneInto(await response.arrayBuffer(), this));
+ EnigmailLog.DEBUG(`wkdLookup.jsm: doWkdKeyDownload: got data for ${email}\n`);
+ return {
+ email: email,
+ keyData: keyData
+ };
+ }
+ catch (ex) {
+ EnigmailLog.DEBUG("wkdLookup.jsm: doWkdKeyDownload: error " + ex.toString() + "\n");
+ return null;
+ }
+ },
+
+ isWkdAvailable: function(email) {
+ let domain = email.toLowerCase().replace(/^.*@/, "");
+
+ return (BLACKLIST_DOMAINS.indexOf(domain) < 0);
+ }
+};
+
+
+/**
+ * Check if enough time has passed since we looked-up the key for "email".
+ *
+ * @param connection: Object - SQLite connection
+ * @param email: String - Email address to search (in lowercase)
+ *
+ * @return Promise (true if new lookup required)
+ */
+function timeForRecheck(connection, email) {
+ EnigmailLog.DEBUG("wkdLookup.jsm: timeForRecheck\n");
+
+ let obj = {
+ email: email,
+ now: Date.now()
+ };
+
+ return connection.execute(
+ "select count(*) from wkd_lookup_timestamp where email = :email and :now - last_seen < 60*60*24", obj
+ ).then(function(val) {
+ return connection.execute(
+ "insert or replace into wkd_lookup_timestamp values (:email, :now)", obj
+ ).then(function() {
+ return Promise.resolve(val);
+ });
+ }).then(function(rows) {
+ EnigmailLog.DEBUG("wkdLookup.jsm: timeForRecheck: " + rows.length + "\n");
+
+ return rows.length === 1 && rows[0].getResultByIndex(0) === 0;
+ }, function(error) {
+ EnigmailLog.DEBUG("wkdLookup.jsm: timeForRecheck - error" + error + "\n");
+ Promise.reject(error);
+ });
+}
+
+/**
+ * Import downloaded keys
+ *
+ * @param {Array of String}: ASCII armored or binary string
+ *
+ * no return value
+ */
+function importDownloadedKeys(keysArr) {
+ EnigmailLog.DEBUG("wkdLookup.jsm: importDownloadedKeys(" + keysArr.length + ")\n");
+
+ let keyData = "";
+ let domainArr = [];
+ for (let k in keysArr) {
+ if (keysArr[k]) {
+ if (keysArr[k].keyData.search(/^-----BEGIN PGP PUBLIC KEY BLOCK-----/) < 0) {
+ try {
+ keyData += EnigmailOpenPGP.enigmailFuncs.bytesToArmor(EnigmailOpenPGP.armor.public_key, keysArr[k].keyData);
+ }
+ catch (ex) {
+ EnigmailLog.DEBUG("wkdLookup.jsm: importDownloadedKeys: exeption=" + ex + "\n");
+ }
+ }
+ else {
+ keyData += keysArr[k].keyData;
+ }
+
+ domainArr.push(keysArr[k].email.replace(/^.*@/, "@"));
+ }
+ }
+
+ let keyList = EnigmailKey.getKeyListFromKeyBlock(keyData, {}, false);
+
+ for (let k in keyList) {
+ EnigmailLog.DEBUG("wkdLookup.jsm: importDownloadedKeys: fpr=" + keyList[k].fpr + "\n");
+ }
+
+ EnigmailKeyRing.importKey(null, false, keyData, "", {}, {}, false, domainArr);
+}
+
+/**
+ * Get special URLs for specific sites that don't use WKD, but still provide
+ * public keys of their users in
+ *
+ * @param {String}: emailAddr: email address in lowercase
+ *
+ * @return {Promise<String>}: URL or null of no URL relevant
+ */
+async function getSiteSpecificUrl(emailAddr) {
+ let domain = emailAddr.replace(/^.+@/, "");
+ let url = null;
+
+ switch (domain) {
+ case "protonmail.ch":
+ case "protonmail.com":
+ case "pm.me":
+ url = "https://api.protonmail.ch/pks/lookup?op=get&options=mr&search=" + escape(emailAddr);
+ break;
+ }
+
+ if (!url) {
+ try {
+ let mxHosts = await EnigmailDns.lookup("MX", domain);
+ if (mxHosts & mxHosts.indexOf("mail.protonmail.ch") >= 0 ||
+ mxHosts.indexOf("mailsec.protonmail.ch") >= 0) {
+ url = "https://api.protonmail.ch/pks/lookup?op=get&options=mr&search=" + escape(emailAddr);
+ }
+ }
+ catch (ex) {}
+ }
+
+ return url;
+}
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/wksMimeHandler.jsm
@@ -0,0 +1,230 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailWksMimeHandler"];
+
+/**
+ * Module for handling response messages from OpenPGP Web Key Service
+ */
+
+const EnigmailCompat = ChromeUtils.import("chrome://openpgp/content/modules/compat.jsm").EnigmailCompat;
+const EnigmailVerify = ChromeUtils.import("chrome://openpgp/content/modules/mimeVerify.jsm").EnigmailVerify;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+const EnigmailDecryption = ChromeUtils.import("chrome://openpgp/content/modules/decryption.jsm").EnigmailDecryption;
+const EnigmailSingletons = ChromeUtils.import("chrome://openpgp/content/modules/singletons.jsm").EnigmailSingletons;
+const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
+
+const APPSHELL_MEDIATOR_CONTRACTID = "@mozilla.org/appshell/window-mediator;1";
+
+var gDebugLog = false;
+
+
+var EnigmailWksMimeHandler = {
+
+ /***
+ * register a PGP/MIME verify object the same way PGP/MIME encrypted mail is handled
+ */
+ registerContentTypeHandler: function() {
+ EnigmailLog.DEBUG("wksMimeHandler.jsm: registerContentTypeHandler()\n");
+ let reg = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+
+ let pgpMimeClass = Components.classes["@mozilla.org/mimecth;1?type=multipart/encrypted"];
+
+ reg.registerFactory(
+ pgpMimeClass,
+ "Enigmail WKD Response Handler",
+ "@mozilla.org/mimecth;1?type=application/vnd.gnupg.wks",
+ null);
+ },
+
+ newHandler: function() {
+ EnigmailLog.DEBUG("wksMimeHandler.jsm: newHandler()\n");
+
+ let v = new PgpWkdHandler();
+ return v;
+ }
+
+};
+
+// MimeVerify Constructor
+function PgpWkdHandler(protocol) {
+ this.inStream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream);
+}
+
+
+// PgpWkdHandler implementation
+PgpWkdHandler.prototype = {
+
+ data: "",
+ mimePartNumber: "",
+ uri: null,
+ backgroundJob: false,
+
+ QueryInterface: EnigmailCompat.generateQI([Ci.nsIStreamListener]),
+
+ onStartRequest: function(request, ctxt) {
+ EnigmailLog.DEBUG("wksMimeHandler.jsm: onStartRequest\n"); // always log this one
+
+ this.mimeSvc = request.QueryInterface(Ci.nsIPgpMimeProxy);
+ let uri = null;
+ if ("messageURI" in this.mimeSvc) {
+ uri = this.mimeSvc.messageURI;
+ }
+ else {
+ uri = ctxt;
+ }
+
+ if ("mimePart" in this.mimeSvc) {
+ this.mimePartNumber = this.mimeSvc.mimePart;
+ }
+ else {
+ this.mimePartNumber = "";
+ }
+ this.data = "";
+ this.msgWindow = EnigmailVerify.lastMsgWindow;
+ this.backgroundJob = false;
+
+ if (this.uri) {
+ this.backgroundJob = (this.uri.spec.search(/[&?]header=(print|quotebody|enigmailConvert)/) >= 0);
+ }
+
+ },
+
+ onDataAvailable: function(req, dummy, stream, offset, count) {
+ if ("messageURI" in this.mimeSvc) {
+ // TB >= 67
+ stream = dummy;
+ count = offset;
+ }
+
+ LOCAL_DEBUG("wksMimeHandler.jsm: onDataAvailable: " + count + "\n");
+ if (count > 0) {
+ this.inStream.init(stream);
+ let data = this.inStream.read(count);
+ this.data += data;
+ }
+ },
+
+ onStopRequest: function() {
+ EnigmailLog.DEBUG("wksMimeHandler.jsm: onStopRequest\n");
+
+ if (this.data.search(/-----BEGIN PGP MESSAGE-----/i) >= 0) {
+ this.decryptChallengeData();
+ }
+
+ let jsonStr = this.requestToJsonString(this.data);
+ let msg = "";
+
+ if (this.data.search(/^\s*type:\s+confirmation-request/mi) >= 0) {
+ msg = EnigmailLocale.getString("wkdMessage.body.req");
+ }
+ else {
+ msg = EnigmailLocale.getString("wkdMessage.body.process");
+ }
+
+ this.returnData(msg);
+ this.displayStatus(jsonStr);
+ },
+
+ decryptChallengeData: function() {
+ EnigmailLog.DEBUG("wksMimeHandler.jsm: decryptChallengeData()\n");
+ let windowManager = Cc[APPSHELL_MEDIATOR_CONTRACTID].getService(Ci.nsIWindowMediator);
+ let win = windowManager.getMostRecentWindow(null);
+ let statusFlagsObj = {};
+
+ let res = EnigmailDecryption.decryptMessage(win,
+ 0,
+ this.data, {}, {}, statusFlagsObj, {}, {}, {}, {}, {}, {});
+
+ if (statusFlagsObj.value & EnigmailConstants.DECRYPTION_OKAY) {
+ this.data = res;
+ }
+ EnigmailLog.DEBUG("wksMimeHandler.jsm: decryptChallengeData: decryption result: " + res + "\n");
+ },
+
+ // convert request data into JSON-string and parse it
+ requestToJsonString: function() {
+ // convert
+ let lines = this.data.split(/\r?\n/);
+ let s = '{';
+ for (let l of lines) {
+ let m = l.match(/^([^\s:]+)(:\s*)([^\s].+)$/);
+ if (m && m.length >= 4) {
+ s += '"' + m[1].trim().toLowerCase() + '": "' + m[3].trim() + '",';
+ }
+ }
+
+ let o = {};
+ s = s.substr(0, s.length - 1) + "}";
+
+ return s;
+ },
+
+ // return data to libMime
+ returnData: function(message) {
+ EnigmailLog.DEBUG("wksMimeHandler.jsm: returnData():\n");
+
+ let msg = 'Content-Type: text/plain; charset="utf-8"\r\n' +
+ 'Content-Transfer-Encoding: 8bit\r\n\r\n' +
+ message + '\r\n';
+
+ if ("outputDecryptedData" in this.mimeSvc) {
+ this.mimeSvc.outputDecryptedData(msg, msg.length);
+ }
+ else {
+ let gConv = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
+ gConv.setData(msg, msg.length);
+ try {
+ this.mimeSvc.onStartRequest(null);
+ this.mimeSvc.onDataAvailable(null, gConv, 0, msg.length);
+ this.mimeSvc.onStopRequest(null, 0);
+ }
+ catch (ex) {
+ EnigmailLog.ERROR("wksMimeHandler.jsm: returnData(): mimeSvc.onDataAvailable failed:\n" + ex.toString());
+ }
+ }
+ },
+
+ displayStatus: function(jsonStr) {
+ EnigmailLog.DEBUG("wksMimeHandler.jsm: displayStatus\n");
+ if (this.msgWindow === null || this.backgroundJob)
+ return;
+
+ try {
+ LOCAL_DEBUG("wksMimeHandler.jsm: displayStatus displaying result\n");
+ let headerSink = EnigmailSingletons.messageReader;
+
+ if (headerSink) {
+ headerSink.processDecryptionResult(this.uri, "wksConfirmRequest", jsonStr, this.mimePartNumber);
+ }
+
+ }
+ catch (ex) {
+ EnigmailLog.writeException("wksMimeHandler.jsm", ex);
+ }
+ }
+};
+
+
+////////////////////////////////////////////////////////////////////
+// General-purpose functions, not exported
+
+function LOCAL_DEBUG(str) {
+ if (gDebugLog) EnigmailLog.DEBUG(str);
+}
+
+function initModule() {
+ var env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
+ var nspr_log_modules = env.get("NSPR_LOG_MODULES");
+ var matches = nspr_log_modules.match(/wksMimeHandler:(\d+)/);
+
+ if (matches && (matches.length > 1)) {
+ if (matches[1] > 2) gDebugLog = true;
+ }
+}
+
+initModule();
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/xhrUtils.jsm
@@ -0,0 +1,135 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+const EXPORTED_SYMBOLS = ["EnigmailXhrUtils"];
+
+var EnigmailXhrUtils = {
+
+ /**
+ * Create an error description from an XMLHttpRequest error.
+ * Adapted from the patch for mozTCPSocket error reporting (bug 861196).
+ *
+ * @param xhr: XMLHttpRequest Object
+ *
+ * @return Object:
+ * - type: String: one of SecurityCertificate, SecurityProtocol, Network
+ * - name: Detailed error text
+ */
+ createTCPErrorFromFailedXHR: function(xhr) {
+ let status = xhr.channel.QueryInterface(Ci.nsIRequest).status;
+
+ let errType;
+ let errName;
+
+ if ((status & 0xff0000) === 0x5a0000) { // Security module
+ const nsINSSErrorsService = Ci.nsINSSErrorsService;
+ let nssErrorsService = Cc['@mozilla.org/nss_errors_service;1'].getService(nsINSSErrorsService);
+ let errorClass;
+ // getErrorClass will throw a generic NS_ERROR_FAILURE if the error code is
+ // somehow not in the set of covered errors.
+ try {
+ errorClass = nssErrorsService.getErrorClass(status);
+ }
+ catch (ex) {
+ errorClass = 'SecurityProtocol';
+ }
+ if (errorClass == nsINSSErrorsService.ERROR_CLASS_BAD_CERT) {
+ errType = 'SecurityCertificate';
+ }
+ else {
+ errType = 'SecurityProtocol';
+ }
+
+ // NSS_SEC errors (happen below the base value because of negative vals)
+ if ((status & 0xffff) < Math.abs(nsINSSErrorsService.NSS_SEC_ERROR_BASE)) {
+ // The bases are actually negative, so in our positive numeric space, we
+ // need to subtract the base off our value.
+ let nssErr = Math.abs(nsINSSErrorsService.NSS_SEC_ERROR_BASE) -
+ (status & 0xffff);
+ switch (nssErr) {
+ case 11: // SEC_ERROR_EXPIRED_CERTIFICATE, sec(11)
+ errName = 'SecurityExpiredCertificateError';
+ break;
+ case 12: // SEC_ERROR_REVOKED_CERTIFICATE, sec(12)
+ errName = 'SecurityRevokedCertificateError';
+ break;
+
+ // per bsmith, we will be unable to tell these errors apart very soon,
+ // so it makes sense to just folder them all together already.
+ case 13: // SEC_ERROR_UNKNOWN_ISSUER, sec(13)
+ case 20: // SEC_ERROR_UNTRUSTED_ISSUER, sec(20)
+ case 21: // SEC_ERROR_UNTRUSTED_CERT, sec(21)
+ case 36: // SEC_ERROR_CA_CERT_INVALID, sec(36)
+ errName = 'SecurityUntrustedCertificateIssuerError';
+ break;
+ case 90: // SEC_ERROR_INADEQUATE_KEY_USAGE, sec(90)
+ errName = 'SecurityInadequateKeyUsageError';
+ break;
+ case 176: // SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED, sec(176)
+ errName = 'SecurityCertificateSignatureAlgorithmDisabledError';
+ break;
+ default:
+ errName = 'SecurityError';
+ break;
+ }
+ }
+ else {
+ let sslErr = Math.abs(nsINSSErrorsService.NSS_SSL_ERROR_BASE) -
+ (status & 0xffff);
+ switch (sslErr) {
+ case 3: // SSL_ERROR_NO_CERTIFICATE, ssl(3)
+ errName = 'SecurityNoCertificateError';
+ break;
+ case 4: // SSL_ERROR_BAD_CERTIFICATE, ssl(4)
+ errName = 'SecurityBadCertificateError';
+ break;
+ case 8: // SSL_ERROR_UNSUPPORTED_CERTIFICATE_TYPE, ssl(8)
+ errName = 'SecurityUnsupportedCertificateTypeError';
+ break;
+ case 9: // SSL_ERROR_UNSUPPORTED_VERSION, ssl(9)
+ errName = 'SecurityUnsupportedTLSVersionError';
+ break;
+ case 12: // SSL_ERROR_BAD_CERT_DOMAIN, ssl(12)
+ errName = 'SecurityCertificateDomainMismatchError';
+ break;
+ default:
+ errName = 'SecurityError';
+ break;
+ }
+ }
+ }
+ else {
+ errType = 'Network';
+ switch (status) {
+ // connect to host:port failed
+ case 0x804B000C: // NS_ERROR_CONNECTION_REFUSED, network(13)
+ errName = 'ConnectionRefusedError';
+ break;
+ // network timeout error
+ case 0x804B000E: // NS_ERROR_NET_TIMEOUT, network(14)
+ errName = 'NetworkTimeoutError';
+ break;
+ // hostname lookup failed
+ case 0x804B001E: // NS_ERROR_UNKNOWN_HOST, network(30)
+ errName = 'DomainNotFoundError';
+ break;
+ case 0x804B0047: // NS_ERROR_NET_INTERRUPT, network(71)
+ errName = 'NetworkInterruptError';
+ break;
+ default:
+ errName = 'NetworkError';
+ break;
+ }
+ }
+
+ return {
+ name: errName,
+ type: errType
+ };
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/zbase32.jsm
@@ -0,0 +1,97 @@
+/* eslint no-invalid-this: 0 */
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailZBase32"];
+
+const ZBase32Alphabet = "ybndrfg8ejkmcpqxot1uwisza345h769";
+
+var EnigmailZBase32 = {
+ a: ZBase32Alphabet,
+ pad: "=",
+
+ /**
+ * Encode a string in Z-Base-32 encoding
+ *
+ * @param str String - input string
+ *
+ * @return String - econded string
+ */
+ encode: function(str) {
+ let a = this.a;
+ let pad = this.pad;
+ let len = str.length;
+ let o = "";
+ let w, c, r = 0,
+ sh = 0;
+
+ for (let i = 0; i < len; i += 5) {
+ // mask top 5 bits
+ c = str.charCodeAt(i);
+ w = 0xf8 & c;
+ o += a.charAt(w >> 3);
+ r = 0x07 & c;
+ sh = 2;
+
+ if ((i + 1) < len) {
+ c = str.charCodeAt(i + 1);
+ // mask top 2 bits
+ w = 0xc0 & c;
+ o += a.charAt((r << 2) + (w >> 6));
+ o += a.charAt((0x3e & c) >> 1);
+ r = c & 0x01;
+ sh = 4;
+ }
+
+ if ((i + 2) < len) {
+ c = str.charCodeAt(i + 2);
+ // mask top 4 bits
+ w = 0xf0 & c;
+ o += a.charAt((r << 4) + (w >> 4));
+ r = 0x0f & c;
+ sh = 1;
+ }
+
+ if ((i + 3) < len) {
+ c = str.charCodeAt(i + 3);
+ // mask top 1 bit
+ w = 0x80 & c;
+ o += a.charAt((r << 1) + (w >> 7));
+ o += a.charAt((0x7c & c) >> 2);
+ r = 0x03 & c;
+ sh = 3;
+ }
+
+ if ((i + 4) < len) {
+ c = str.charCodeAt(i + 4);
+ // mask top 3 bits
+ w = 0xe0 & c;
+ o += a.charAt((r << 3) + (w >> 5));
+ o += a.charAt(0x1f & c);
+ r = 0;
+ sh = 0;
+ }
+ }
+ // Calculate length of pad by getting the
+ // number of words to reach an 8th octet.
+ if (r != 0) {
+ o += a.charAt(r << sh);
+ }
+ var padlen = 8 - (o.length % 8);
+
+ if (padlen === 8) {
+ return o;
+ }
+
+ if (padlen === 1 || padlen === 3 || padlen === 4 || padlen === 6) {
+ return o + pad.repeat(padlen);
+ }
+
+ throw 'there was some kind of error:\npadlen:' + padlen + ' ,r:' + r + ' ,sh:' + sh + ', w:' + w;
+ }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/strings/enigmail.dtd
@@ -0,0 +1,740 @@
+<!ENTITY enigmail.label "Gine-Liam">
+<!ENTITY enigmail.autocrypt.label "Autocrypt">
+
+<!ENTITY enigmail.keyUserId.label "Account / User ID">
+<!ENTITY enigmail.keygenTitle.label "Generate OpenPGP Key">
+<!ENTITY enigmail.useForSigning.label "Use generated key for the selected identity">
+<!ENTITY enigmail.keyNoPassphrase.label "No passphrase">
+<!ENTITY enigmail.keyPassphrase.label "Passphrase">
+<!ENTITY enigmail.keyPassphraseRepeat.label "Passphrase (repeat)">
+<!ENTITY enigmail.generateKey.tooltip "Generates a new OpenPGP compliant key for encryption and/or signing">
+<!ENTITY enigmail.cancelKey.label "Cancel">
+<!ENTITY enigmail.cancelKey.tooltip "Cancel Key Generation">
+<!ENTITY enigmail.keyGen.expiry.title "Key expiry">
+<!ENTITY enigmail.keyGen.expire.label "Key expires in">
+<!ENTITY enigmail.keyGen.days.label "days">
+<!ENTITY enigmail.keyGen.months.label "months">
+<!ENTITY enigmail.keyGen.years.label "years">
+<!ENTITY enigmail.keyGen.noExpiry.label "Key does not expire">
+<!ENTITY enigmail.keyGen.keySize.label "Key size">
+<!ENTITY enigmail.keyGen.console.label "Key Generation">
+<!ENTITY enigmail.keyGen.keyType.label "Key type">
+<!ENTITY enigmail.keyGen.keyType.rsa "RSA">
+<!ENTITY enigmail.keyGen.keyType.ecc "ECC (Elliptic Curve)">
+
+<!ENTITY enigmail.preferences.label "Gine-Liam Preferences">
+
+<!ENTITY enigmail.passwordSettings.label "Passphrase Timeout">
+<!ENTITY enigmail.expertUserMenus.label "Display Expert Settings and Menus">
+<!ENTITY enigmail.expertUser.tooltip "Press if you want to see menu items and options for advanced users.">
+<!ENTITY enigmail.basicUserMenus.label "Hide Expert Settings and Menus">
+<!ENTITY enigmail.basicUser.tooltip "Press if you do not want to see menu items and options for advanced users.">
+
+<!ENTITY enigmail.prefs.pathsTitle.label "Files and Directories">
+<!ENTITY enigmail.prefs.overrideGpg.label "Override with">
+<!ENTITY enigmail.prefs.overrideGpg.tooltip "Click to specify a location for GnuPG">
+
+
+<!ENTITY enigmail.agentAdditionalParam.label "Additional parameters for GnuPG">
+<!ENTITY enigmail.agentAdditionalParam.tooltip "Specify options that will be used by GnuPG.">
+
+<!ENTITY enigmail.protectHeadersPrefs.label "Encrypt subject by default (PGP/MIME encrypted message only)">
+<!ENTITY enigmail.protectHeadersPrefs.tooltip "Remove subject and other sensitive headers from encrypted messages.">
+<!ENTITY enigmail.protectedSubjectText.label "Subject text to protect encrypted messages">
+<!ENTITY enigmail.protectedSubjectText.tooltip "Specify the subject that will be publicly visible instead of the real subject when sending encrypted messages">
+<!ENTITY enigmail.protectedSubjectText.placeholder "Encrypted Message">
+
+
+
+<!ENTITY enigmail.mime_parts_on_demand.label "Only download attachments when opened (IMAP only)">
+
+
+<!ENTITY enigmail.maxIdleMinutesHead.label "Remember passphrase for">
+<!ENTITY enigmail.maxIdleMinutesTail.label "minutes of inactivity (before needing to re-enter it)">
+<!ENTITY enigmail.maxIdleMinutes.tooltip "Remember passphrase for a limited time.">
+
+<!ENTITY enigmail.resetPrefsButton.label "Reset">
+<!ENTITY enigmail.resetPrefs.tooltip "Set all Gine-Liam preferences back to their default value.">
+
+<!ENTITY enigmail.backupPrefsButton.label "Export Settings and Keys">
+<!ENTITY enigmail.backupPrefs.tooltip "Save all Gine-Liam preferences including your OpenPGP keys to a file.">
+
+<!ENTITY enigmail.restorePrefsButton.label "Restore Settings and Keys">
+<!ENTITY enigmail.restorePrefs.tooltip "Restore all Gine-Liam preferences including your OpenPGP keys from a file.">
+
+<!ENTITY enigmail.pref.acSetupMessage.button "Initiate Autocrypt Setup">
+
+<!ENTITY enigmail.backupRestore.desc "Export (backup) and Restore your Gine-Liam settings including your OpenPGP keys.">
+
+<!ENTITY enigmail.basic.label "Basic">
+<!ENTITY enigmail.sending.label "Sending">
+<!ENTITY enigmail.keySel.label "Key Selection">
+<!ENTITY enigmail.pgpMime.label "PGP/MIME">
+<!ENTITY enigmail.advanced.label "Advanced">
+<!ENTITY enigmail.transferSettings.label "Transfer Settings">
+
+<!ENTITY enigmail.basicPrefs.label "Basic Settings">
+
+<!ENTITY enigmail.moreOptions.label "More Options">
+<!ENTITY enigmail.general.okLabel "OK">
+
+<!ENTITY enigmail.useNonDefaultComment.label "Add Gine-Liam comment in OpenPGP signature">
+
+<!ENTITY enigmail.keyserver.label "Keyserver">
+<!ENTITY enigmail.keyserverDlg.label "Select Keyserver">
+<!ENTITY enigmail.keyservers.label "Specify your keyserver(s):">
+<!ENTITY enigmail.keyservers.sample2 "Example: keys.openpgp.org, pool.sks-keyservers.net">
+
+<!ENTITY enigmail.searchForKey.example "Example: 'Forename Surname <user@example.com>'">
+<!ENTITY enigmail.searchForKey.desc1 "'Forename Surname' finds all keys that contain 'Forename' OR 'Surname'">
+<!ENTITY enigmail.searchForKey.desc2 "'Forename+Surname' finds all keys that contain both 'Forename' AND 'Surname'">
+<!ENTITY enigmail.searchForKey.desc3 "'example.com' finds all keys that contain 'example.com', such as 'example@example.com'">
+<!ENTITY enigmail.searchForKey.label "Search for key">
+
+<!ENTITY enigmail.doubleDashSeparator.label "'--' is a signature separator">
+<!ENTITY enigmail.noHushMailSupport.label "Use '<' and '>' to specify email addresses">
+
+<!ENTITY enigmail.doubleDashSeparator.tooltip "Two dashes by themselves on a line will denote a signature block">
+<!ENTITY enigmail.noHushMailSupport.tooltip "Use the '<' and '>' characters to specify email addresses for GnuPG. Disable if recipients have old Hushmail keys.">
+<!ENTITY enigmail.mime_parts_on_demand.tooltip "You should turn this OFF if you are receiving PGP/MIME messages on IMAP folders.">
+<!ENTITY enigmail.disableSMIMEui.tooltip "Remove the S/MIME button from the message composition window toolbar">
+
+<!ENTITY enigmail.autoKeyServerSelection.label "Always use first keyserver">
+<!ENTITY enigmail.autoKeyServerSelection.tooltip "Take the first keyserver from the list as default">
+
+<!ENTITY enigmail.decryptbutton.label "Decrypt">
+<!ENTITY enigmail.decryptbutton.tip "Decrypt or verify the message with Gine-Liam">
+
+<!ENTITY enigmail.doneButton.label "Done">
+<!ENTITY enigmail.doneButton.accesskey "D">
+
+<!ENTITY enigmail.messengermenu.accesskey "a">
+
+<!ENTITY enigmail.decryptverify.label "Decrypt/Verify">
+<!ENTITY enigmail.importpublickey.label "Import Public Key">
+<!ENTITY enigmail.importKey.label "Import Key">
+
+<!ENTITY enigmail.autoDecrypt.label "Automatically Decrypt/Verify Messages">
+<!ENTITY enigmail.clearPassphrase.label "Clear Saved Passphrase">
+<!ENTITY enigmail.editRules.label "Edit Per-Recipient Rules">
+
+<!ENTITY enigmail.decryptToFolder.label "Decrypt to folder">
+<!ENTITY enigmail.decryptToFolder.accesskey "y">
+
+<!-- "recent" label for Folder selection submenu -->
+<!ENTITY enigmail.recent.label "Recent">
+<!ENTITY enigmail.recent.accesskey "R">
+
+<!ENTITY enigmail.prefs.label "Preferences">
+
+<!ENTITY enigmail.debugMenu.label "Debugging Options">
+<!ENTITY enigmail.viewconsole.label "View Console">
+<!ENTITY enigmail.viewlog.label "View Log">
+
+<!ENTITY enigmail.generateKey.label "Generate key">
+<!ENTITY enigmail.help.label "Help">
+<!ENTITY enigmail.about.label "About Gine-Liam">
+<!ENTITY enigmail.reload.label "Reload Message">
+<!ENTITY enigmail.browse.label "Browse...">
+<!ENTITY enigmail.sendersKeyMenu.label "Sender's Key">
+<!ENTITY enigmail.adminSmartCard.label "Manage SmartCard ...">
+<!ENTITY enigmail.setupWiz.label "Setup Wizard">
+
+<!ENTITY enigmail.decryptverify.accesskey "D">
+<!ENTITY enigmail.importpublickey.accesskey "I">
+<!ENTITY enigmail.reload.accesskey "E">
+<!ENTITY enigmail.adminSmartCard.accesskey "M">
+
+<!ENTITY enigmail.autoDecrypt.accesskey "A">
+<!ENTITY enigmail.debugMenu.accesskey "g">
+
+<!ENTITY enigmail.clearPassphrase.accesskey "l">
+<!ENTITY enigmail.editRules.accesskey "R">
+<!ENTITY enigmail.setupWiz.accesskey "S">
+
+
+<!ENTITY enigmail.prefs.accesskey "P">
+<!ENTITY enigmail.viewconsole.accesskey "c">
+<!ENTITY enigmail.viewlog.accesskey "g">
+
+<!ENTITY enigmail.help.accesskey "H">
+<!ENTITY enigmail.about.accesskey "b">
+<!ENTITY enigmail.sendersKeyMenu.accesskey "K">
+
+<!ENTITY enigmail.composetoolbar.desc "Gine-Liam Encryption Info">
+
+<!ENTITY enigmail.signedsend.label "Sign Message">
+<!ENTITY enigmail.encryptedsend.label "Encrypt Message">
+
+<!ENTITY enigmail.pgpAccountSettings.label "Signing/Encryption Options...">
+<!ENTITY enigmail.sendOptionsMenu.label "Send Options...">
+<!ENTITY enigmail.defaultKeySelOpts.label "Key Selection Options...">
+<!ENTITY enigmail.usePgpMime.label "Protocol: PGP/MIME">
+<!ENTITY enigmail.useInline.label "Protocol: Inline PGP">
+<!ENTITY enigmail.useSmime.label "Protocol: S/MIME">
+
+<!ENTITY enigmail.tempTrustAllKeys.label "Trust the keys of all recipients">
+<!ENTITY enigmail.undoencryption.label "Undo Encryption">
+<!ENTITY enigmail.attachkey.label "Attach Public Key ...">
+<!ENTITY enigmail.attachmykey.label "Attach My Public Key">
+<!ENTITY enigmail.attachmykey.short.label "Attach Key">
+<!ENTITY enigmail.protectHeaders.label "Protect Subject">
+
+<!ENTITY enigmail.composemenu.accesskey "n">
+
+<!ENTITY enigmail.signedsend.accesskey "S">
+<!ENTITY enigmail.signedsend.key "S">
+<!ENTITY enigmail.encryptedsend.accesskey "E">
+<!ENTITY enigmail.encryptedsend.key "E">
+
+<!ENTITY enigmail.sendOptionsMenu.accesskey "S">
+<!ENTITY enigmail.defaultKeySelOpts.accesskey "K">
+<!ENTITY enigmail.pgpAccountSettings.accesskey "E">
+<!ENTITY enigmail.usePgpMime.accesskey "G">
+<!ENTITY enigmail.useInline.accesskey "I">
+<!ENTITY enigmail.useSmime.accesskey "S">
+
+<!ENTITY enigmail.tempTrustAllKeys.accesskey "T">
+<!ENTITY enigmail.undoencryption.accesskey "U">
+<!ENTITY enigmail.attachkey.accesskey "A">
+<!ENTITY enigmail.attachmykey.accesskey "M">
+<!ENTITY enigmail.protectHeaders.accesskey "O">
+
+
+<!-- account specific options -->
+<!ENTITY enigmail.defaultSigning.label "Sign messages by default">
+<!ENTITY enigmail.defaultEncryption.label "Encrypt messages by default">
+<!ENTITY enigmail.usePGPMimeAlways.label "Use PGP/MIME by default">
+
+<!ENTITY enigmail.afterDefaultsAndRules.label "After application of defaults and rules:">
+<!ENTITY enigmail.finallySignEncrypted.label "Sign encrypted messages">
+<!ENTITY enigmail.finallySignNotEncrypted.label "Sign non-encrypted messages">
+
+<!ENTITY enigmail.autoEncryptDrafts.label "Encrypt draft messages on saving">
+
+<!ENTITY enigmail.advancedPrefsButton.label "Advanced...">
+<!ENTITY enigmail.openpgpPrefsButton.label "Gine-Liam Preferences...">
+
+
+<!-- Sending Preferences -->
+
+<!ENTITY enigmail.SendingPrefs.label "General Preferences for Sending">
+<!ENTITY enigmail.seeAccountSettings.label "See also account specific Gine-Liam settings (see Account Settings -> OpenPGP Security)">
+
+<!ENTITY enigmail.encryptionModelConvenient.label "Use default encryption settings">
+<!ENTITY enigmail.encryptionModelManually.label "Manually configure encryption settings">
+
+<!ENTITY enigmail.keepCryptoSettingsForReply.label "Encrypt/sign replies to encrypted/signed messages">
+<!ENTITY enigmail.keepCryptoSettingsForReply.tooltip "When possible, encrypt/sign responses to encrypted/signed email">
+
+<!ENTITY enigmail.autoSendEncryptedOption.label "Automatically send encrypted">
+<!ENTITY enigmail.autoSendEncryptedNever.label "Never">
+<!ENTITY enigmail.autoSendEncryptedNever.tooltip "Don't automatically send encrypted except explicitly triggered by per-recipient rules">
+<!ENTITY enigmail.autoSendEncryptedIfKeys.label "If possible">
+<!ENTITY enigmail.autoSendEncryptedIfKeys.tooltip "Automatically send encrypted when for each recipient at least one key is known and accepted (see above) and no per-recipient rule forces not to encrypt.">
+
+<!ENTITY enigmail.acceptedKeysOption.label "To send encrypted, accept">
+<!ENTITY enigmail.acceptedKeysTrusted.label "Only trusted keys">
+<!ENTITY enigmail.acceptedKeysTrusted.tooltip "Accept only keys that are signed by yourself or by other people you trust. Technically, you only accept keys 'valid' for encryption.">
+<!ENTITY enigmail.acceptedKeysAllUsable.label "All usable keys">
+<!ENTITY enigmail.acceptedKeysAllUsable.tooltip "Accept all keys that are neither disabled by you nor revoked/expired by the owner. Technically, this uses the GPG option '--trust-model always'.">
+
+<!ENTITY enigmail.confirmBeforeSendingOption.label "Confirm before sending">
+<!ENTITY enigmail.confirmBeforeSendingNever.label "Never">
+<!ENTITY enigmail.confirmBeforeSendingNever.tooltip "Don't display information dialog about signing/encryption before sending a message">
+<!ENTITY enigmail.confirmBeforeSendingAlways.label "Always">
+<!ENTITY enigmail.confirmBeforeSendingAlways.tooltip "Always display information dialog about signing/encryption before sending a message">
+<!ENTITY enigmail.confirmBeforeSendingIfEncrypted.label "If encrypted">
+<!ENTITY enigmail.confirmBeforeSendingIfEncrypted.tooltip "Display information dialog about signing/encryption before sending an ENCRYPTED message">
+<!ENTITY enigmail.confirmBeforeSendingIfNotEncrypted.label "If unencrypted">
+<!ENTITY enigmail.confirmBeforeSendingIfNotEncrypted.tooltip "Display information dialog about signing/encryption before sending an UNENCRYPTED message">
+<!ENTITY enigmail.confirmBeforeSendingIfRules.label "If rules changed the default encryption setting">
+<!ENTITY enigmail.confirmBeforeSendingIfRules.tooltip "Display information dialog about signing/encryption before sending, if your preference regarding encryption was changed by a per-recipient rule or auto encryption">
+
+
+<!-- keySel Preferences -->
+
+<!ENTITY enigmail.recipientsSelectionOption.label "How should we choose the keys?">
+<!ENTITY enigmail.assignKeysByRules.label "By Per-Recipient Rules">
+<!ENTITY enigmail.assignKeysByRules.tooltip "Assign keys according to per-recipient rules. Force to prompt for creating new rules if this is the only selected option.">
+<!ENTITY enigmail.assignKeysByEmailAddr.label "By Email Addresses according to the key manager">
+<!ENTITY enigmail.assignKeysByEmailAddr.tooltip "Assign keys according to keys associated with the email addresses in the key manager.">
+<!ENTITY enigmail.assignKeysManuallyIfMissing.label "Manually if Keys are Missing">
+<!ENTITY enigmail.assignKeysManuallyIfMissing.tooltip "Open dialog to manually assign keys if keys are missing.">
+<!ENTITY enigmail.assignKeysManuallyAlways.label "Always (also) Manually">
+<!ENTITY enigmail.assignKeysManuallyAlways.tooltip "Always finally open the dialog to manually assign keys.">
+<!ENTITY enigmail.defineRules.label "Edit Rules ...">
+
+
+<!ENTITY enigmail.wrapHtmlBeforeSend.label "Re-wrap signed HTML text before sending">
+<!ENTITY enigmail.wrapHtmlBeforeSend.tooltip "Rearrange the text to ensure that the signature will remain intact.">
+<!ENTITY enigmail.autoKeyRetrieve.label "Automatically download keys for signature verification">
+<!ENTITY enigmail.autoKeyRetrieve2.label "from the following keyserver:">
+<!ENTITY enigmail.resetRememberedValues.label "Reset Warnings">
+<!ENTITY enigmail.resetRememberedValues.tooltip "Re-display all warning dialogs and questions that you have hidden in the past.">
+
+<!ENTITY enigmail.keygen.desc "<a class='enigmailStrong'>NOTE: Key generation may take up to several minutes to complete.</a> Do not exit the application while key generation is in progress. Actively browsing or performing disk-intensive operations during key generation will replenish the 'randomness pool' and speed-up the process. You will be alerted when key generation is completed.">
+
+<!ENTITY enigmail.userSelectionList.label "Gine-Liam Key Selection">
+<!ENTITY enigmail.usersNotFound.label "Recipients not valid, not trusted or not found">
+<!ENTITY enigmail.keyExpiry.label "Expiry">
+<!ENTITY enigmail.uidValidity.label "Validity">
+<!ENTITY enigmail.keySelection.label "Selection">
+<!ENTITY enigmail.keyId.label "Key ID">
+<!ENTITY enigmail.userSelSendSigned.label "Send signed">
+<!ENTITY enigmail.userSelSendSigned.accesskey "S">
+<!ENTITY enigmail.userSelSendEncrypted.label "Send encrypted">
+<!ENTITY enigmail.userSelSendEncrypted.accesskey "E">
+<!ENTITY enigmail.displayNoLonger.label "Do not display this dialog again if encryption is not possible">
+<!ENTITY enigmail.importMissingKeys.label "Download missing keys">
+<!ENTITY enigmail.importMissingKeys.accesskey "D">
+<!ENTITY enigmail.importMissingKeys.tooltip "Try to import the missing keys from a key server">
+<!ENTITY enigmail.refreshKeys.label "Refresh Key List">
+<!ENTITY enigmail.refreshKeys.accesskey "R">
+<!ENTITY enigmail.send.label "Send">
+<!ENTITY enigmail.perRecipientsOption.label "Create per-recipient rule(s)">
+
+<!ENTITY enigmail.enigmailAttachDesc.label "This message contains attachments. How would you like encrypt/sign them?">
+<!ENTITY enigmail.enigEncryptAttachNone.label "Just encrypt/sign the message text, but not the attachments">
+<!ENTITY enigmail.enigAttachNoneSign.label "Sign the message text, but not the attachments">
+<!ENTITY enigmail.enigAttachNoneEncrypt.label "Encrypt the message text, but not the attachments">
+<!ENTITY enigmail.enigAttachNoneEncryptAndSign.label "Encrypt and sign the message text, but not the attachments">
+<!ENTITY enigmail.enigEncryptAttachInline.label "Encrypt/sign each attachment separately and send the message using inline PGP">
+<!ENTITY enigmail.enigAttachInlineSign.label "Sign each attachment separately and send the message using inline PGP">
+<!ENTITY enigmail.enigAttachInlineEncrypt.label "Encrypt each attachment separately and send the message using inline PGP">
+<!ENTITY enigmail.enigAttachInlineEncryptAndSign.label "Encrypt and sign each attachment separately and send the message using inline PGP">
+<!ENTITY enigmail.enigEncryptAttachPgpMime.label "Encrypt/sign the message as a whole and send it using PGP/MIME">
+<!ENTITY enigmail.enigAttachPgpMimeSign.label "Sign the message as a whole and send it using PGP/MIME">
+<!ENTITY enigmail.enigAttachPgpMimeEncrypt.label "Encrypt the message as a whole and send it using PGP/MIME">
+<!ENTITY enigmail.enigAttachPgpMimeEncryptAndSign.label "Encrypt and sign the message as a whole and send it using PGP/MIME">
+<!ENTITY enigmail.enigEncryptAttachDontEncryptMsg.label "Don't encrypt/sign the message at all">
+<!ENTITY enigmail.enigAttachDontEncryptMsgSign.label "Don't sign the message at all">
+<!ENTITY enigmail.enigAttachDontEncryptMsgEncrypt.label "Don't encrypt the message at all">
+<!ENTITY enigmail.enigAttachDontEncryptMsgEncryptAndSign.label "Don't encrypt and sign the message at all">
+<!ENTITY enigmail.encryptAttachSkipDlg.label "Use the selected method for all future attachments">
+
+
+<!ENTITY enigmail.ctxDecryptOpen.label "Decrypt and Open">
+<!ENTITY enigmail.ctxDecryptSave.label "Decrypt and Save As...">
+<!ENTITY enigmail.ctxImportKey.label "Import OpenPGP Key">
+<!ENTITY enigmail.ctxVerifyAtt.label "Verify signature">
+<!ENTITY enigmail.ctxDecryptOpen.accesskey "D">
+<!ENTITY enigmail.ctxDecryptSave.accesskey "C">
+<!ENTITY enigmail.ctxImportKey.accesskey "I">
+<!ENTITY enigmail.ctxVerifyAtt.accesskey "V">
+<!ENTITY enigmail.detailsHdrButton.label "Details">
+<!ENTITY enigmail.revealAttachmentsButton.label "The names of the files attached are hidden. Press the 'Reveal' button to retrieve the original file names.">
+<!ENTITY enigmail.revealAttachments.button "Reveal">
+
+<!ENTITY enigmail.exchangeGarbage.desc "This is a broken PGP/MIME message from MS-Exchange. If you cannot access the attachments or have problems with replying or forwarding, press the 'Repair message' button to fix the message">
+<!ENTITY enigmail.exchangeGarbage.fixButton.label "Repair message">
+<!ENTITY enigmail.exchangeGarbage.waitMessage "Please wait ...">
+
+<!ENTITY enigmail.amPrefTitle.label "Signing/Encryption Options...">
+<!ENTITY enigmail.amPrefDesc.label "Support for OpenPGP encryption and signing messages is provided by Gine-Liam. You need to have GnuPG (gpg) installed in order to use this feature.">
+<!ENTITY enigmail.amPrefEnablePgp.label "Enable OpenPGP support (Gine-Liam) for this identity">
+<!ENTITY enigmail.amPrefUseFromAddr.label "Use email address of this identity to identify OpenPGP key">
+<!ENTITY enigmail.amPrefUseKeyId.label "Use specific OpenPGP key ID:">
+<!ENTITY enigmail.amPrefSelectKey.label "Select Key">
+<!ENTITY enigmail.amPrefMsgComp.label "Message Composition">
+<!ENTITY enigmail.amPrefDefaultEncrypt.label "Message Composition Default Options">
+<!ENTITY enigmail.encryptionDlg.label "Gine-Liam Encryption & Signing Settings">
+<!ENTITY enigmail.encDlgProtocol.label "Encryption/Signing Standard">
+<!ENTITY enigmail.encDlgEncrypt.label "Encrypt Message">
+<!ENTITY enigmail.encDlgEncrypt.accesskey "E">
+<!ENTITY enigmail.encDlgSign.label "Sign Message">
+<!ENTITY enigmail.encDlgSign.accesskey "S">
+<!ENTITY enigmail.encDlgPgpMime.label "Use PGP/MIME">
+<!ENTITY enigmail.encDlgPgpMime.accesskey "P">
+<!ENTITY enigmail.encDlgInlinePgp.label "Use Inline PGP">
+<!ENTITY enigmail.encDlgInlinePgp.accesskey "I">
+<!ENTITY enigmail.encDlgSMime.label "Use S/MIME">
+<!ENTITY enigmail.encDlgSMime.accesskey "M">
+<!ENTITY enigmail.encDlgReset.label "Reset to Defaults">
+<!ENTITY enigmail.encDlgReset.accesskey "R">
+<!ENTITY enigmail.amPrefPgpHeader.label "Send 'OpenPGP' Header">
+<!ENTITY enigmail.amPrefPgpHeader.id.label "Send OpenPGP Key ID">
+<!ENTITY enigmail.amPrefPgpHeader.url.label "Send URL for key retrieval:">
+<!ENTITY enigmail.amPrefMimePreferProto.label "If both, Gine-Liam and S/MIME encryption are possible, then:">
+<!ENTITY enigmail.amPrefMimePreferEnigmail.label "Prefer Gine-Liam (OpenPGP)">
+<!ENTITY enigmail.amPrefMimePreferSMime.label "Prefer S/MIME">
+
+<!ENTITY enigmail.progressText.label "Progress:">
+
+<!ENTITY enigmail.openPgpSecurity.label "OpenPGP Security">
+
+<!ENTITY enigmail.pgpSecurityInfo.label "Gine-Liam Security Info ...">
+<!ENTITY enigmail.copySecurityInfo.label "Copy Gine-Liam Security Info">
+<!ENTITY enigmail.showPhoto.label "View OpenPGP Photo ID">
+<!ENTITY enigmail.photoViewer.title "OpenPGP Photo Viewer">
+<!ENTITY enigmail.displayKeyProperties.label "View Key Properties">
+<!ENTITY enigmail.signSenderKey.label "Sign Key ...">
+<!ENTITY enigmail.trustSenderKey.label "Set Owner Trust ...">
+<!ENTITY enigmail.signSenderKeyPopup.label "Sign Sender's Key ...">
+<!ENTITY enigmail.trustSenderKeyPopup.label "Set Owner Trust of Sender's Key...">
+<!ENTITY enigmail.createRuleFromAddr.label "Create Gine-Liam Rule from Address...">
+<!ENTITY enigmail.keyManWindow.label "Key Management">
+<!ENTITY enigmail.keyManWindow.accesskey "y">
+
+<!ENTITY enigmail.singleRcptSettings.label "Gine-Liam - Recipient Settings">
+
+<!ENTITY enigmail.compose.excesslength.label "You have very long lines in your email. They should be handled differently to avoid bad signatures.">
+<!ENTITY enigmail.compose.excesslength_action.label "How do you want to proceed?">
+<!ENTITY enigmail.wrapSelectionList.label "Select wrap">
+<!ENTITY enigmail.compose.wrap_radio.label "Wrap automatically (good signature, but may not look good)">
+<!ENTITY enigmail.compose.sendasis_radio.label "Send as is (and risk a bad signature)">
+<!ENTITY enigmail.compose.use_mime_radio.label "Use PGP/MIME (good signature, but some receivers cannot decode it)">
+<!ENTITY enigmail.compose.editmanually.label "Edit manually">
+
+<!ENTITY enigmail.selKeysButton.label "Select Key(s)...">
+<!ENTITY enigmail.action.label "Action">
+<!ENTITY enigmail.nextRule.label "Continue with next rule for the matching address">
+<!ENTITY enigmail.nextAddress.label "Do not check further rules for the matching address">
+<!ENTITY enigmail.useKeys.label "Use the following OpenPGP keys:">
+<!ENTITY enigmail.selKeysButton.accesskey "S">
+<!ENTITY enigmail.setDefaultsFor.label "Defaults for ...">
+<!ENTITY enigmail.encryption.label "Encryption">
+<!ENTITY enigmail.signing.label "Signing">
+<!ENTITY enigmail.never.label "Never">
+<!ENTITY enigmail.always.label "Always">
+<!ENTITY enigmail.maybe.label "Yes, if selected in Message Composition">
+<!ENTITY enigmail.singleRcptSettings.desc "(Note: in case of conflicts, 'Never' overrules 'Always')">
+<!ENTITY enigmail.ruleEmail.label "Set Gine-Liam Rules for">
+<!ENTITY enigmail.ruleEmail.tooltip "Only fill in email addresses, do NOT set the names of the recipients\nE.g. 'Some Name <some.name@address.net>' is invalid">
+<!ENTITY enigmail.sepratationDesc.label "(Separate several email addresses with spaces)">
+<!ENTITY enigmail.matchDescStart.label "Apply rule if recipient">
+<!ENTITY enigmail.matchDescEnd.label "one of the above addresses">
+<!ENTITY enigmail.matchExact.label "is exactly">
+<!ENTITY enigmail.matchContains.label "contains">
+<!ENTITY enigmail.matchBegin.label "starts with">
+<!ENTITY enigmail.matchEnd.label "ends with">
+<!ENTITY enigmail.not.label "Not">
+
+<!ENTITY enigmail.rulesEditor.label "Gine-Liam - Per-Recipient Rules Editor">
+<!ENTITY enigmail.email.label "Email">
+<!ENTITY enigmail.pgpKeys.label "OpenPGP Key(s)">
+<!ENTITY enigmail.sign.label "Sign">
+<!ENTITY enigmail.encrypt.label "Encrypt">
+<!ENTITY enigmail.edit.label "Modify">
+<!ENTITY enigmail.edit.accesskey "M">
+<!ENTITY enigmail.add.label "Add">
+<!ENTITY enigmail.add.accesskey "A">
+<!ENTITY enigmail.delete.label "Delete">
+<!ENTITY enigmail.delete.accesskey "E">
+<!ENTITY enigmail.moveUp.label "Move Up">
+<!ENTITY enigmail.moveUp.accesskey "U">
+<!ENTITY enigmail.moveDown.label "Move Down">
+<!ENTITY enigmail.moveDown.accesskey "D">
+<!ENTITY enigmail.searchRule.label "View rules with email addresses containing:">
+<!ENTITY enigmail.clearSearch.label "Clear">
+<!ENTITY enigmail.clearSearch.accesskey "C">
+
+<!ENTITY enigmail.searchKeyDlg.label "Download OpenPGP Keys">
+<!ENTITY enigmail.searchKeyDlgCapt.label "Found Keys - Select to Import">
+<!ENTITY enigmail.searchKeyDlgSelAll.label "Select/Deselect all">
+
+<!ENTITY enigmail.created.label "Created">
+
+<!ENTITY enigmail.progress.label "Progress">
+
+<!ENTITY enigmail.sKeyDlg.title "Gine-Liam - Sign Key">
+<!ENTITY enigmail.sKeyDlg.signKey.label "Key to be signed:">
+<!ENTITY enigmail.sKeyDlg.fingerprint.label "Fingerprint:">
+<!ENTITY enigmail.sKeyDlg.signWithKey.label "Key for signing:">
+<!ENTITY enigmail.sKeyDlg.ownKeyTrust.label "Note: you have to set owner trust to ultimate for your own keys to be shown here.">
+<!ENTITY enigmail.sKeyDlg.checked.label "How carefully have you verified that the key you are about to sign actually belongs to the person(s) named above?">
+<!ENTITY enigmail.sKeyDlg.noAnswer.label "I will not answer">
+<!ENTITY enigmail.sKeyDlg.notChecked.label "I have not checked at all">
+<!ENTITY enigmail.sKeyDlg.casualCheck.label "I did a quick check">
+<!ENTITY enigmail.sKeyDlg.carefulCheck.label "I have checked very carefully">
+<!ENTITY enigmail.sKeyDlg.createLocal.label "Local signature (cannot be exported)">
+
+<!ENTITY enigmail.addUidDlg.title "Gine-Liam - Add User ID">
+<!ENTITY enigmail.addUidDlg.name.label "Name">
+<!ENTITY enigmail.addUidDlg.email.label "Email">
+
+<!ENTITY enigmail.keyTrust.title "Gine-Liam - Set Owner Trust">
+<!ENTITY enigmail.keyTrust.trustKey.label "Key To Trust:">
+<!ENTITY enigmail.keyTrust.trustLevel.label "How much do you trust the owner of the key to sign other keys properly?">
+<!ENTITY enigmail.keyTrust.trustLevel.tooltip "For further information about keytrust levels visit: https://gpgtools.tenderapp.com/kb/faq/what-is-ownertrust-trust-levels-explained">
+<!ENTITY enigmail.keyTrust.dontKnow.label "Unknown (Not yet specified)">
+<!ENTITY enigmail.keyTrust.noTrust.label "Not Trusted (Not trusted to sign keys properly)">
+<!ENTITY enigmail.keyTrust.marginalTrust.label "Marginal (Somewhat trusted to sign keys properly)">
+<!ENTITY enigmail.keyTrust.fullTrust.label "Complete (Fully trusted to sign keys properly)">
+<!ENTITY enigmail.keyTrust.ultimateTrust.label "Ultimate (Only use for keys you own yourself)">
+
+<!ENTITY enigmail.keyExpiry.title "Gine-Liam - Change Expiration Date">
+<!ENTITY enigmail.keyExpiry.expiryKey.label "Keys to change expiration date:">
+
+<!ENTITY enigmail.keyMan.title "Gine-Liam Key Management">
+<!ENTITY enigmail.keyMan.close.label "Close Window">
+<!ENTITY enigmail.keyMan.generate.label "New Key Pair">
+<!ENTITY enigmail.keyMan.sign.label "Sign Key">
+<!ENTITY enigmail.keyMan.setTrust.label "Set Owner Trust">
+<!ENTITY enigmail.keyMan.genRevoke.label "Revocation Certificate">
+<!ENTITY enigmail.keyMan.ctxGenRevoke.label "Generate & Save Revocation Certificate">
+
+<!ENTITY enigmail.keyMan.fileMenu.label "File">
+<!ENTITY enigmail.keyMan.editMenu.label "Edit">
+<!ENTITY enigmail.keyMan.viewMenu.label "View">
+<!ENTITY enigmail.keyMan.keyserverMenu.label "Keyserver">
+<!ENTITY enigmail.keyMan.generateMenu.label "Generate">
+
+<!ENTITY enigmail.keyMan.fileMenu.accesskey "F">
+<!ENTITY enigmail.keyMan.editMenu.accesskey "E">
+<!ENTITY enigmail.keyMan.viewMenu.accesskey "V">
+<!ENTITY enigmail.keyMan.keyserverMenu.accesskey "K">
+<!ENTITY enigmail.keyMan.generateMenu.accesskey "G">
+
+<!ENTITY enigmail.keyMan.importFromFile.label "Import Keys from File">
+<!ENTITY enigmail.keyMan.importFromServer.label "Search for Keys">
+<!ENTITY enigmail.keyMan.importFromClipbrd.label "Import Keys from Clipboard">
+<!ENTITY enigmail.keyMan.importFromUrl.label "Import Keys from URL">
+<!ENTITY enigmail.keyMan.exportToFile.label "Export Keys to File">
+<!ENTITY enigmail.keyMan.sendKeys.label "Send Public Keys by Email">
+<!ENTITY enigmail.keyMan.createMail.label "Compose Email to Selected Keys">
+<!ENTITY enigmail.keyMan.uploadToServer.label "Upload Public Keys">
+<!ENTITY enigmail.keyMan.uploadToWkd.label "Upload to your provider's Web Key Directory">
+<!ENTITY enigmail.keyMan.copyToClipbrd.label "Copy Public Keys to Clipboard">
+<!ENTITY enigmail.keyMan.ctxExportToFile.label "Export Keys to File">
+<!ENTITY enigmail.keyMan.ctxUploadToServer.label "Upload Public Keys to Keyserver">
+<!ENTITY enigmail.keyMan.ctxCopyToClipbrd.label "Copy Public Keys to Clipboard">
+<!ENTITY enigmail.keyMan.refreshSelKeys.label "Refresh Selected Public Keys">
+<!ENTITY enigmail.keyMan.refreshAllKeys.label "Refresh All Public Keys">
+<!ENTITY enigmail.keyMan.downloadContactKeys.label "Find keys for all contacts">
+<!ENTITY enigmail.keyMan.ctxRefreshKey.label "Refresh Public Keys From Keyserver">
+<!ENTITY enigmail.keyMan.initiateAcBackup.label "Create Autocrypt Setup Message">
+
+<!ENTITY enigmail.keyMan.reload.label "Reload Key Cache">
+<!ENTITY enigmail.keyMan.addPhoto.label "Add Photo">
+<!ENTITY enigmail.keyMan.manageUid.label "Manage User IDs">
+<!ENTITY enigmail.keyMan.changeExpiry.label "Change Expiration Date">
+<!ENTITY enigmail.keyMan.changePwd.label "Change Passphrase">
+<!ENTITY enigmail.keyMan.delKey.label "Delete Key">
+<!ENTITY enigmail.keyMan.revokeKey.label "Revoke Key">
+<!ENTITY enigmail.keyMan.keyProps.label "Key Properties">
+<!ENTITY enigmail.keyMan.showAllKeys.label "Display All Keys by Default">
+<!ENTITY enigmail.keyMan.viewPhoto.label "Photo ID">
+<!ENTITY enigmail.keyMan.ctxViewPhoto.label "View Photo ID">
+<!ENTITY enigmail.keyMan.showInvalidKeys.label "Display invalid keys">
+<!ENTITY enigmail.keyMan.showUntrustedKeys.label "Display untrusted keys">
+<!ENTITY enigmail.keyMan.showOthersKeys.label "Display keys from other people">
+<!ENTITY enigmail.keyMan.userId.label "Name">
+<!ENTITY enigmail.keyMan.keyType.label "Type">
+<!ENTITY enigmail.keyMan.calcTrust.label "Key Validity">
+<!ENTITY enigmail.keyMan.ownerTrust.label "Owner Trust">
+<!ENTITY enigmail.keyMan.fingerprint.label "Fingerprint">
+<!ENTITY enigmail.keyMan.selectAll.label "Select all Keys">
+<!ENTITY enigmail.keyMan.addToPRRule.label "Add to Per-Recipient Rule">
+<!ENTITY enigmail.keyMan.emptyTree.tooltip "Enter search terms in the box above">
+<!ENTITY enigmail.keyMan.nothingFound.tooltip "No keys match your search terms">
+<!ENTITY enigmail.keyMan.pleaseWait.tooltip "Please wait while keys are being loaded ...">
+
+<!ENTITY enigmail.keyMan.filter.label "Search for keys">
+
+<!ENTITY enigmail.keyMan.close.accesskey "C">
+<!ENTITY enigmail.keyMan.generate.accesskey "K">
+<!ENTITY enigmail.keyMan.sign.accesskey "S">
+<!ENTITY enigmail.keyMan.setTrust.accesskey "T">
+<!ENTITY enigmail.keyMan.genRevoke.accesskey "R">
+<!ENTITY enigmail.keyMan.delKey.accesskey "D">
+<!ENTITY enigmail.keyMan.revokeKey.accesskey "R">
+<!ENTITY enigmail.keyMan.importFromFile.accesskey "I">
+<!ENTITY enigmail.keyMan.exportToFile.accesskey "E">
+<!ENTITY enigmail.keyMan.importFromServer.accesskey "S">
+<!ENTITY enigmail.keyMan.uploadToServer.accesskey "U">
+<!ENTITY enigmail.keyMan.reload.accesskey "R">
+<!ENTITY enigmail.keyMan.addPhoto.accesskey "P">
+<!ENTITY enigmail.keyMan.manageUid.accesskey "M">
+<!ENTITY enigmail.keyMan.changeExpiry.accesskey "E">
+<!ENTITY enigmail.keyMan.changePwd.accesskey "P">
+<!ENTITY enigmail.keyMan.showInvalidKeys.accesskey "D">
+<!ENTITY enigmail.keyMan.showUntrustedKeys.accesskey "U">
+<!ENTITY enigmail.keyMan.showOthersKeys.accesskey "O">
+<!ENTITY enigmail.keyMan.showPhoto.accesskey "P">
+<!ENTITY enigmail.keyMan.importFromClipbrd.accesskey "I">
+<!ENTITY enigmail.keyMan.importFromUrl.accesskey "U">
+<!ENTITY enigmail.keyMan.copyToClipbrd.accesskey "C">
+<!ENTITY enigmail.keyMan.keyDetails.accesskey "K">
+<!ENTITY enigmail.keyMan.showAllKeys.accesskey "D">
+<!ENTITY enigmail.keyMan.enableKey.accesskey "B">
+<!ENTITY enigmail.keyMan.refreshSelKeys.accesskey "R">
+<!ENTITY enigmail.keyMan.refreshAllKeys.accesskey "A">
+<!ENTITY enigmail.keyMan.selectAll.accesskey "A">
+<!ENTITY enigmail.keyMan.sendKeys.accesskey "S">
+<!ENTITY enigmail.keyMan.createMail.accesskey "C">
+<!ENTITY enigmail.keyMan.downloadContactKeys.accesskey "F">
+<!ENTITY enigmail.keyMan.initiateAcBackup.accesskey "A">
+
+
+<!ENTITY enigmail.keyMan.selectAll.key "A">
+<!ENTITY enigmail.keyMan.keyDetails.key "I">
+<!ENTITY enigmail.keyMan.refreshKey.key "R">
+
+<!ENTITY enigmail.keySearch.selectAll.key "A">
+
+<!ENTITY enigmail.manageUidDlg.title "Change Primary User ID">
+<!ENTITY enigmail.manageUidDlg.affectedKey.label "Key to change:">
+<!ENTITY enigmail.manageUidDlg.availableUid.label "Available user IDs:">
+<!ENTITY enigmail.manageUidDlg.addUid.label "Add">
+<!ENTITY enigmail.manageUidDlg.revokeUid.label "Revoke">
+<!ENTITY enigmail.manageUidDlg.setPrimary.label "Set primary">
+
+<!ENTITY enigmail.keyDetails.title "Key Properties">
+<!ENTITY enigmail.keyDetails.signaturesTab "Certifications">
+<!ENTITY enigmail.keyDetails.structureTab "Structure">
+<!ENTITY enigmail.keyDetails.uidCertifiedCol "User ID / Certified by">
+<!ENTITY enigmail.keyDetails.userId.label "Primary User ID">
+<!ENTITY enigmail.keyDetails.ID.label "ID">
+<!ENTITY enigmail.keyDetails.keyType.label "Type">
+<!ENTITY enigmail.keyDetails.keyPart.label "Key Part">
+<!ENTITY enigmail.keyDetails.algorithm.label "Algorithm">
+<!ENTITY enigmail.keyDetails.size.label "Size">
+<!ENTITY enigmail.keyDetails.created.label "Created">
+<!ENTITY enigmail.keyDetails.expiry.label "Expiry">
+<!ENTITY enigmail.keyDetails.usage.label "Usage">
+<!ENTITY enigmail.keyDetails.fingerprint.label "Fingerprint">
+<!ENTITY enigmail.keyDetails.selAction.label "Select action ...">
+<!ENTITY enigmail.keyDetails.selAction.accesskey "S">
+<!ENTITY enigmail.keyDetails.change.label "Change">
+<!ENTITY enigmail.keyDetails.signKey.label "Certify">
+<!ENTITY enigmail.keyDetails.trustStatus.label "You rely on certifications">
+<!ENTITY enigmail.keyDetails.keyValidity.label "Validity">
+<!ENTITY enigmail.keyDetails.alsoKnown.label "Also known as">
+
+<!ENTITY enigmail.cardDetails.title "OpenPGP SmartCard Details">
+<!ENTITY enigmail.cardDetails.cardMenu.label "SmartCard">
+<!ENTITY enigmail.cardDetails.adminPin.label "Change PIN">
+<!ENTITY enigmail.cardDetails.genCardKey.label "Generate Key">
+
+<!ENTITY enigmail.cardDetails.vendor.label "Manufacturer">
+<!ENTITY enigmail.cardDetails.serial.label "Serial Number">
+<!ENTITY enigmail.cardDetails.name.label "Firstname, Name">
+<!ENTITY enigmail.cardDetails.lang.label "Language">
+<!ENTITY enigmail.cardDetails.sex.label "Gender">
+<!ENTITY enigmail.cardDetails.url.label "URL of public key">
+<!ENTITY enigmail.cardDetails.login.label "Login data">
+<!ENTITY enigmail.cardDetails.forcepin.label "Force signature PIN">
+<!ENTITY enigmail.cardDetails.maxpinlen.label "Max. PIN length">
+<!ENTITY enigmail.cardDetails.pinretry.label "PIN retry counter">
+<!ENTITY enigmail.cardDetails.sigcount.label "Signature counter">
+<!ENTITY enigmail.cardDetails.sigKey.label "Signature key">
+<!ENTITY enigmail.cardDetails.keyCreated.label "Created">
+<!ENTITY enigmail.cardDetails.encKey.label "Encryption key">
+<!ENTITY enigmail.cardDetails.authKey.label "Authentication key">
+<!ENTITY enigmail.cardDetails.yes.label "Yes">
+<!ENTITY enigmail.cardDetails.no.label "No">
+<!ENTITY enigmail.cardDetails.male.label "Male">
+<!ENTITY enigmail.cardDetails.female.label "Female">
+<!ENTITY enigmail.cardDetails.editData.label "Edit Card Data">
+<!ENTITY enigmail.cardDetails.save.label "Save">
+<!ENTITY enigmail.cardDetails.reset.label "Reset">
+<!ENTITY enigmail.cardDetails.closeWindow.label "Close">
+
+<!ENTITY enigmail.cardDetails.adminPin.accesskey "P">
+<!ENTITY enigmail.cardDetails.genCardKey.accesskey "G">
+<!ENTITY enigmail.cardDetails.cardMenu.accesskey "S">
+<!ENTITY enigmail.cardDetails.editData.accesskey "E">
+
+<!ENTITY enigmail.genCardKey.title "Generate OpenPGP Key">
+<!ENTITY enigmail.genCardKey.backupKey.label "Save backup of key outside the card">
+<!ENTITY enigmail.genCardKey.desc "<a class='enigmailStrong'>NOTE: Key generation may take up to several minutes to complete.</a> Do not exit the application while key generation is in progress. You will be alerted when key generation is completed.">
+
+<!ENTITY enigmail.cardPin.title "Change SmartCard PIN">
+<!ENTITY enigmail.cardPin.action.label "What do you want to do">
+<!ENTITY enigmail.cardPin.changePin.label "Change PIN">
+<!ENTITY enigmail.cardPin.changeAdmPin.label "Change Admin PIN">
+<!ENTITY enigmail.cardPin.unblockPin.label "Unblock PIN">
+<!ENTITY enigmail.cardPin.currAdmPin.label "Current ADMIN PIN">
+<!ENTITY enigmail.cardPin.newAdminPin.label "New admin PIN">
+<!ENTITY enigmail.cardPin.adminPinRepeat.label "Repeat new PIN">
+<!ENTITY enigmail.cardPin.currPin.label "Current PIN">
+<!ENTITY enigmail.cardPin.newPin.label "New PIN">
+<!ENTITY enigmail.cardPin.pinRepeat.label "Repeat new PIN">
+
+<!-- wizard: welcome step -->
+<!ENTITY enigmail.setupWiz.title "Gine-Liam Setup">
+<!ENTITY enigmail.setupWiz.pgIntro.desc "It looks like you started Gine-Liam for the first time on this computer.
+ In order to use Gine-Liam, you need to first set it up properly.
+ This assistant will guide you through the setup process.">
+<!-- wizard: install GnuPG step -->
+<!ENTITY enigmail.setupWiz.pgInstall.desc "Gine-Liam requires GnuPG for doing the cryptographic work.
+ The wizard tried to locate the GnuPG executable, but it could not be found.">
+<!ENTITY enigmail.setupWiz.pgInstall.installDesc1 "Gine-Liam can download GnuPG and launch the installer for you.
+ If you wish to do so, click on the 'Install GnuPG' button to download and install GnuPG.">
+<!ENTITY enigmail.setupWiz.pgInstall.installDesc2 "Alternatively, if you already installed GnuPG, use the 'Browse' button to specify the path to GnuPG manually.">
+
+<!ENTITY enigmail.setupWiz.pgInstall.locateDesc "Please locate GnuPG using the Browse button.">
+<!ENTITY enigmail.setupWiz.pgInstall.installButton "Install GnuPG ...">
+<!ENTITY enigmail.setupWiz.pgInstall.locateButton "Browse ...">
+<!ENTITY enigmail.setupWiz.pgInstall.downloadLabel "Downloading GnuPG ...">
+<!ENTITY enigmail.setupWiz.pgInstall.installLabel "Starting the installer. Please follow the steps of the GnuPG installer wizard.">
+
+<!ENTITY enigmail.setupWiz.searchingGnuPG "Looking for GnuPG ...">
+<!ENTITY enigmail.setupWiz.checkingSetup "Checking your existing setup ...">
+<!ENTITY enigmail.setupWiz.startSetupButton "Start Setup">
+<!ENTITY enigmail.setupWiz.useImportKeys "Alternatively, you can import your keys using the 'Import Keys' button below.">
+<!ENTITY enigmail.setupWiz.existingEncryptedMsg "We found that you have encrypted messages in your mail folders. We recommend that you export your keys on your existing device, and import them here. (Hint: you may use the 'Import Keys' multiple times, for example to import public and secret keys).">
+<!ENTITY enigmail.setupWiz.existingKeysFound "We found that you already have OpenPGP keys. We recommend that we set up Gine-Liam for you so that you can continue to work with these keys.">
+<!ENTITY enigmail.setupWiz.applyExistingKeysBtn "Apply my Keys">
+<!ENTITY enigmail.setupWiz.rescanInboxBtn "Rescan Inbox">
+<!ENTITY enigmail.setupWiz.importKeysBtn "Import Keys">
+
+<!ENTITY enigmail.advancedIdentityDlg.title "Advanced Gine-Liam Identity Settings">
+<!ENTITY enigmail.amPrefPgp.sendKeyWithMsg.label "Attach my public key to messages">
+
+<!ENTITY enigmail.addPhoto.question.label "Do you want to add the following picture to the key?">
+<!ENTITY enigmail.addPhoto.title "Add Photo to Key">
+<!ENTITY enigmail.msgViewColumn.tooltip "Sort by the OpenPGP status">
+<!ENTITY enigmail.addToRule.title "Add Key to Per-Recipient Rule">
+<!ENTITY enigmail.addToRule.useRuleButton.label "Add Key to Selected Rule">
+<!ENTITY enigmail.addToRule.useRuleButton.accesskey "A">
+<!ENTITY enigmail.addToRule.newRuleButton.label "Create New Rule">
+<!ENTITY enigmail.addToRule.newRuleButton.accesskey "N">
+
+<!ENTITY enigmail.savelogfile.label "Save Log to File ...">
+
+<!ENTITY enigmail.filterEditor.moveError.label "Actions that follow the 'Decrypt Permanently' action are not supported.">
+
+<!-- Export wizard -->
+<!ENTITY enigmail.exportWiz.title "Export your Gine-Liam Settings">
+<!ENTITY enigmail.exportWiz.pgIntro.title "Welcome to the Export Settings Wizard">
+<!ENTITY enigmail.exportWiz.pgIntro.desc "Gine-Liam allows to transfer all its settings including your OpenPGP
+ keys to a new computer. This is done in two steps:">
+<!ENTITY enigmail.exportWiz.pgIntro.step1 "export your data from your old computer using this wizard">
+<!ENTITY enigmail.exportWiz.pgIntro.step2 "import the data to your new computer using the Setup Wizard.">
+<!ENTITY enigmail.exportWiz.pgIntro.specifyFile "Please specify the export file:">
+<!ENTITY enigmail.exportWiz.pgExport.title "Exporting Gine-Liam Settings">
+<!ENTITY enigmail.exportWiz.pgExport.exporting "Saving in progress. You might be asked for the passphrase(s) of your key(s).">
+<!ENTITY enigmail.exportWiz.pgExport.done "Your OpenPGP keys and your Gine-Liam settings are now saved.
+ Please transfer the generated file to your new computer, launch
+ Thunderbird and start the Setup Wizard from the Gine-Liam menu.">
+<!ENTITY enigmail.exportWiz.pgExport.failed "An error occurred during the export. The settings could not be exported.">
+
+
+<!ENTITY enigmail.acPreferEncrypt.label "Prefer encrypted emails from the people you exchange email with">
+<!ENTITY enigmail.enableAutocrypt.label "Enable Autocrypt">
+
+<!ENTITY enigmail.acSetupPasswd.dlgTitle "Autocrypt Setup Code">
+<!ENTITY enigmail.acSetupPasswd.password "Setup Code:">
+
+<!ENTITY enigmail.acKeyTransfer.window.title "Autocrypt Key Transfer">
+<!ENTITY enigmail.acKeyTransfer.welcome.title "Start Key Transfer">
+<!ENTITY enigmail.acKeyTransfer.welcome.desc1 "If you have another device that is Autocrypt-enabled, you can transfer your key to the other device via a so-called 'Autocrypt Setup Message'.
+ Such a message will allow your other device to send and receive encrypted emails just like Gine-Liam with just a few clicks.">
+<!ENTITY enigmail.acKeyTransfer.welcome.desc2 "Please select below the account for which you want to transfer your key.
+ We will display a setup code (password) on the following screen that you will need to enter on the other device in order to import your key and related settings.">
+
+<!ENTITY enigmail.acKeyTransfer.selectAccount.label "Create Setup Message for Account:">
+<!ENTITY enigmail.acKeyTransfer.displayPasswd.title "Code for Autocrypt Setup Message">
+<!ENTITY enigmail.acKeyTransfer.msgCreated.desc1 "The setup message has been sent to yourself.">
+<!ENTITY enigmail.acKeyTransfer.msgCreated.desc2 "Please switch to the other device now and open the message. You should be prompted for a setup code. Please type the following digits into the prompt:">
+<!ENTITY enigmail.acKeyTransfer.switchDevice.desc "Once you're done, your other device will be ready to use Autocrypt.">
+
+<!ENTITY enigmail.importSetup.dlgTitle "Restore Settings and Keys">
+<!ENTITY enigmail.importSetup.topDesc "Using this dialog, you can restore your settings and keys from an existing Gine-Liam installation. To create a backup of your settings and keys, use the 'Transfer Settings' tab in the Gine-Liam preferences.">
+<!ENTITY enigmail.importSetup.specifyImportFile "Please specify the backup file to use for restoring your settings and keys:">
+<!ENTITY enigmail.importSetup.startRestoreLabel "Start Restoring">
+<!ENTITY enigmail.importSetup.importingKeys "Importing keys in GnuPG. This may take a while ...">
+<!ENTITY enigmail.importSetup.restoringGnupgSettings "Restoring GnuPG settings ...">
+<!ENTITY enigmail.importSetup.restoringEnigmail "Restoring Gine-Liam settings ...">
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/strings/enigmail.properties
@@ -0,0 +1,758 @@
+Enigmail=Gine-Liam
+
+
+#####################################################################
+# Strings used within enigmailCommon.js and enigmailCommon.jsm
+#####################################################################
+
+enigAlert=Gine-Liam Alert
+enigConfirm=Gine-Liam Confirmation
+enigInfo=Gine-Liam Information
+enigError=Gine-Liam Error
+enigPrompt=Gine-Liam Prompt
+
+dlgYes=&Yes
+dlgNo=&No
+dlgKeepSetting=Remember my answer and do not ask me again
+dlgNoPrompt=Do not show me this dialog again
+dlg.button.delete=&Delete
+dlg.button.cancel=&Cancel
+dlg.button.close=&Close
+dlg.button.continue=Con&tinue
+dlg.button.skip=&Skip
+dlg.button.overwrite=&Overwrite
+dlg.button.view=&View
+dlg.button.retry=&Retry
+dlg.button.ignore=&Ignore
+dlg.button.install=&Install
+dlg.button.ok=&OK
+
+repeatPrefix=\n\nThis alert will repeat %S
+repeatSuffixSingular=more time.
+repeatSuffixPlural=more times.
+noRepeat=\n\nThis alert will not repeat until you upgrade Gine-Liam.
+
+pgpNotSupported=You seem to be using Gine-Liam together with PGP 6.x\n\nUnfortunately, PGP 6.x has a number of issues that prevent Gine-Liam from working correctly. Therefore, Gine-Liam does not support PGP 6.x anymore; please switch to GnuPG (GPG) instead.\n\nIf you need help on switching to GnuPG, check the Help section of the Gine-Liam homepage.
+initErr.howToFixIt=In order to use Gine-Liam, GnuPG is required. If you did not install GnuPG yet, the easiest way to do this is using the "Setup Wizard" button below.
+initErr.setupWizard.button=&Setup Wizard
+passphraseCleared=The passphrase has been cleared.
+cannotClearPassphrase=You are using a non-standard tool (such as gnome-keyring) for passphrase handling. Clearing the passphrase is therefore not possible from within Gine-Liam.
+noPhotoAvailable=No Photo available
+debugLog.title=Gine-Liam Debug Log
+error.photoPathNotReadable=Photo path '%S' is not readable
+
+generalError=Error: %S
+
+# Strings in configure.jsm
+enigmailCommon.versionSignificantlyChanged=This new version of Gine-Liam has significant changes in the handling of preferences and options. We tried to transfer the old settings to this new version. However, we cannot cover all cases automatically. Please double check the resulting new preferences and options.
+enigmailCommon.checkPreferences=Check Preferences ...
+preferences.defaultToPgpMime=We have changed the default message encoding in Gine-Liam from Inline-PGP to PGP/MIME. We recommend you keep this as default.\n\nIf you still wish to use Inline-PGP by default, you can do so in the Account Settings under OpenPGP Security.
+
+#####################################################################
+# Strings in enigmailAbout.js
+#####################################################################
+
+usingVersion=Running Gine-Liam version %S
+usingAgent=Using %1$S executable %2$S to encrypt and decrypt
+agentError=ERROR: Failed to access Gine-Liam core service!
+
+#####################################################################
+# Strings in enigmailKeygen.js
+#####################################################################
+
+accessError=Error in accessing Gine-Liam service
+onlyGPG=Key generation only works with GnuPG (not with PGP)!
+
+keygenComplete=Key generation completed! Identity <%S> will be used for signing.
+revokeCertRecommended=We highly recommend to create a revocation certificate for your key. This certificate can be used to invalidate your key, e.g. in case your secret key gets lost or compromised. Do you want to create such a revocation certificate now?
+keyMan.button.generateCert=&Generate Certificate
+genCompleteNoSign=Key generation completed!
+genGoing=Key generation already in progress!
+
+passNoMatch=Passphrase entries do not match; please re-enter
+passCheckBox=Please check box if specifying no passphrase for key
+passUserName=Please specify user name for this identity
+keygen.missingUserName=There is no name specified for the selected account/identity. Please enter a value in the field "Your name" in the account settings.
+keygen.passCharProblem=You are using special characters in your passphrase. Unfortunately, this can cause troubles for other applications. We recommend you choose a passphrase consisting only of any of these characters:\na-z A-Z 0-9 /.;:-,!?(){}[]%*
+passSpaceProblem=Due to technical reasons, your passphrase may not start or end with a space character.
+changePassFailed=Changing the passphrase failed.
+
+keyConfirm=Generate public and secret key for '%S'?
+keyMan.button.generateKey=&Generate Key
+keyAbort=Abort key generation?
+keyMan.button.generateKeyAbort=&Abort Key Generation
+keyMan.button.generateKeyContinue=&Continue Key Generation
+expiryTooLong=You cannot create a key that expires in more than 100 years.
+expiryTooLongShorter=You cannot create a key that expires in more than 90 years.
+expiryTooShort=Your key must be valid for at least one day.
+keyGenFailed=The key generation failed. Please check the Gine-Liam console (Menu Gine-Liam > Debugging Options) for details.
+setKeyExpirationDateFailed=The expiration date could not be changed
+
+# Strings in enigmailMessengerOverlay.js
+securityInfo=Gine-Liam Security Info\n\n
+
+enigHeader=Gine-Liam:
+enigContentNote=Gine-Liam: *Attachments to this message have not been signed nor encrypted*\r\n\r\n
+possiblyPgpMime=Possibly PGP/MIME encrypted or signed message; use 'Decrypt/Verify' function to verify
+
+saveAttachmentHeader=Gine-Liam: Save decrypted attachment
+noTempDir=Could not find a temporary directory to write to\nPlease set the TEMP environment variable
+attachmentPgpKey=The attachment '%S' you are opening appears to be an OpenPGP key file.\n\nClick 'Import' to import the keys contained or 'View' to view the file contents in a browser window
+
+beginPgpPart=********* *BEGIN ENCRYPTED or SIGNED PART* *********
+endPgpPart=********** *END ENCRYPTED or SIGNED PART* **********
+notePartEncrypted=Gine-Liam: *Parts of the message have NOT been signed nor encrypted*
+noteCutMessage=Gine-Liam: *Multiple message blocks found -- decryption/verification aborted*
+
+decryptOkNoSig=Warning\n\nDecryption was successful, but the signature could not be verified correctly
+msgOvl.button.contAnyway=&Continue Anyway
+signature.verifiedOK=The signature for attachment %S was successfully verified
+signature.verifyFailed=The signature for attachment %S could not be verified
+attachment.noMatchToSignature=Could not match attachment '%S' to a signature file
+attachment.noMatchFromSignature=Could not match signature file '%S' to an attachment
+fixBrokenExchangeMsg.failed=Did not succeed to repair message.
+enigmail.msgViewColumn.label=Gine-Liam
+detailsDlg.importKey=Import key
+wksNoIdentity=This key is not linked to any of your email accounts. Please add an account for at least one of the following email addresse(s):\n\n%S
+wksConfirmSuccess=Confirmation email sent.
+wksConfirmFailure=Sending the confirmation email failed.
+autocrypt.importSetupKey.accountPreconfigured=Your account is already correctly configured for Autocrypt.\n\nDo you really want to overwrite your settings with this setup message?
+autocrypt.importSetupKey.selfCreated=This message was created by your currently running instance of Gine-Liam.\n\nPlease switch to the email client to which you want to transfer the settings, and click on the message on that email client to import the settings.
+autocrypt.importSetupKey.invalidMessage=Error - could not read setup message. The message seems to be corrupted. Please try to create a new setup message on your "other" device.
+autocrypt.importSetupKey.invalidKey=Error - the key could not be imported. The key is either not supported by your version of GnuPG, or it got corrupted.
+autocrypt.importSetupKey.wrongPasswd=The password you entered is wrong. Do you want to retry?
+autocrypt.importSetupKey.success=The Autocrypt setup message was processed successfully. Autocrypt is now available for your account '%S'.
+
+#####################################################################
+# Strings in enigmailMsgComposeOverlay.js
+#####################################################################
+
+keysToExport=Select OpenPGP Keys to Insert
+keysToUse=Select OpenPGP Key(s) to use for %S
+pubKey=Public key for %S\n
+
+windowLocked=Compose window is locked; send cancelled
+sendUnencrypted=Failed to initialize Gine-Liam.\nSend unencrypted message?
+composeSpecifyEmail=Please specify your primary email address, which will be used to choose the signing key for outgoing messages.\n If you leave it blank, the FROM address of the message will be used to choose the signing key.
+sendingHiddenRcpt=This message has BCC (blind copy) recipients. If this message is encrypted, it is possible to hide the BCC recipients but users of some products (e.g. PGP Corp.) will not be able to decrypt the message. Given this, we recommend to avoid BCC-emails with encrypted messages.
+sendWithHiddenBcc=Hide BCC recipients
+sendWithShownBcc=Encrypt normally
+sendingNews=Encrypted send operation aborted.\n\nThis message cannot be encrypted because there are newsgroup recipients. Please re-send the message without encryption.
+sendToNewsWarning=Warning: you are about to send an encrypted email to a newsgroup.\n\nThis is discouraged because it only makes sense if all members of the group can decrypt the message, i.e. the message needs to be encrypted with the keys of all group participants. Please send this message only if you know exactly what you are doing.\n\nContinue?
+hasHTML=HTML email warning:\nThis message may contain HTML, which could cause signing/encryption to fail. To avoid this in the future, you should press the SHIFT key when clicking on the Compose/Reply button to send signed email.\nIf you sign email by default, you should uncheck the 'Compose Messages in HTML' preference box to permanently disable HTML email for this email account.
+strippingHTML=Message contains HTML formatting information that will be lost when converting to plain text for signing/encryption. Do you wish to proceed?
+msgCompose.button.sendAnyway=&Send Message Anyway
+attachWarning=Attachments to this message are not local, they cannot be encrypted. In order to encrypt the attachments, store them as local files first and attach these files. Do you wish to send the message anyway?
+quotedPrintableWarn=You have enabled 'quoted-printable' encoding for sending messages. This may result in incorrect decryption and/or verification of your message.\nDo you wish to turn off sending 'quoted-printable' messages now?
+minimalLineWrapping=You have set line wrapping to %S characters. For correct encryption and/or signing, this value needs to be at least 68.\nDo you wish to change line wrapping to 68 characters now?
+warning=Warning
+signIconClicked=You have manually modified signing. Therefore, while you are composing this message, (de)activating signing does not depend anymore on (de)activating encryption.
+errorOwnKeyUnusable=The key ID '%S' configured for the current identity does not yield a usable OpenPGP key.\n\nPlease ensure that you have a valid, not expired OpenPGP key and that your account settings point to that key.\nIf your key is not expired, then check if you did set Owner trust to "full" or "ultimate".
+msgCompose.cannotSaveDraft=Error while saving draft
+msgCompose.partiallyEncrypted.short=Beware of leaking sensitive information - partially encrypted email.
+msgCompose.partiallyEncrypted.inlinePGP=The message you are replying to contained both unencrypted and encrypted parts. If the sender was not able to decrypt some message parts originally, you may be leaking confidential information that the sender was not able to originally decrypt themselves.\n\nPlease consider removing all quoted text from your reply to this sender.
+
+msgCompose.internalEncryptionError=Internal Error: promised encryption disabled
+msgCompose.internalError=An internal error has occurred.
+
+msgCompose.toolbarTxt.signAndEncrypt=This message will be signed and encrypted
+msgCompose.toolbarTxt.signOnly=This message will be signed
+msgCompose.toolbarTxt.encryptOnly=This message will be encrypted
+msgCompose.toolbarTxt.noEncryption=This message will be unsigned and unencrypted
+msgCompose.toolbarTxt.disabled=Gine-Liam is disabled for the selected identity
+msgCompose.protectSubject.tooltip=Protect the message subject
+msgCompose.noSubjectProtection.tooltip=Do not protect the message subject
+msgCompose.protectSubject.dialogTitle=Enable Protection of Subject?
+msgCompose.protectSubject.question=Regular encrypted emails contain the unredacted subject.\n\nWe have established a standard to hide the original subject in the encrypted message\nand replace it with a dummy text, such that the subject is only visible after the email is decrypted.\n\nDo you want to protect the subject in encrypted messages?
+msgCompose.protectSubject.yesButton=&Protect subject
+msgCompose.protectSubject.noButton=&Leave subject unprotected
+
+msgCompose.detailsButton.label=Details ...
+msgCompose.detailsButton.accessKey=D
+
+# note: should end with double newline:
+sendAborted=Send operation aborted.\n\n
+
+# details:
+keyNotTrusted=Not enough trust for key '%S'
+keyNotFound=Key '%S' not found
+keyRevoked=Key '%S' revoked
+keyExpired=Key '%S' expired
+
+statPGPMIME=PGP/MIME
+statSMIME=S/MIME
+statSigned=SIGNED
+statEncrypted=ENCRYPTED
+statPlain=UNSIGNED and UNENCRYPTED
+
+offlineSave=Save %1$S message to %2$S in Unsent Messages folder?
+
+onlineSend=Send %1$S message to %2$S?
+encryptKeysNote=Note: The message is encrypted for the following User IDs / Keys: %S
+hiddenKey=<hidden key>
+
+signFailed=Error in Gine-Liam: encryption/signing failed. Send the message unencrypted?
+msgCompose.button.sendUnencrypted=&Send Unencrypted Message
+recipientsSelectionHdr=Select Recipients for Encryption
+
+configureNow=You did not yet configure Gine-Liam security for the selected identity. Do you want to do this now?
+
+# encryption/signing status and associated reasons:
+encryptMessageAuto=Encrypt Message (auto)
+encryptMessageNorm=Encrypt Message
+signMessageAuto=Sign Message (auto)
+signMessageNorm=Sign Message
+
+encryptOff=Encryption: OFF
+encryptOnWithReason=Encryption: ON (%S)
+encryptOffWithReason=Encryption: OFF (%S)
+encryptOn=Encryption: ON
+signOn=Signing: ON
+signOff=Signing: OFF
+signOnWithReason=Signing: ON (%S)
+signOffWithReason=Signing: OFF (%S)
+reasonEnabledByDefault=enabled by default
+reasonManuallyForced=manually forced
+reasonByRecipientRules=forced by per-recipient rules
+reasonByAutoEncryption=forced by auto encryption
+reasonByConflict=due to conflict in per-recipient rules
+reasonByEncryptionMode=due to encryption mode
+
+# should not be used anymore:
+encryptYes=Message will be encrypted
+encryptNo=Message will not be encrypted
+
+# should not be used anymore:
+signYes=Message will be signed
+signNo=Message will not be signed
+
+
+# PGP/MIME status:
+pgpmimeNormal=Protocol: PGP/MIME
+inlinePGPNormal=Protocol: Inline PGP
+smimeNormal=Protocol: S/MIME
+pgpmimeAuto=Protocol: PGP/MIME (auto)
+inlinePGPAuto=Protocol: Inline PGP (auto)
+smimeAuto=Protocol: S/MIME (auto)
+
+# should not be used anymore
+pgpmimeYes=PGP/MIME will be used
+pgpmimeNo=Inline PGP will be used
+
+# Attach own key status (tooltip strings):
+attachOwnKeyNo=Your own public key will not be attached
+attachOwnKeyYes=Your own public key will be attached
+attachOwnKeyDisabled=Your own public key cannot be attached. You have to select a specific key\nin the OpenPGP section of the Account Settings to enable this feature.
+
+rulesConflict=Conflicting per-recipient rules detected\n%S\n\nSend message with these settings?
+msgCompose.button.configure=&Configure
+msgCompose.button.send=&Send Message
+msgCompose.button.save=&Save Message
+
+# Strings in enigmailMsgHdrViewOverlay.js
+keyNeeded=Public key %S needed to verify signature
+keyUsed=Public key %S used to verify signature
+clickDecrypt=; use 'Decrypt/Verify' function
+clickDecryptRetry=; use 'Decrypt/Verify' function to retry
+clickDetailsButton=; click on 'Details' button for more information
+clickImportButton=; click on the 'Import Key' button to import the key
+keyTypeUnsupported=; the key type is not supported by your version of GnuPG
+decryptManually=; click on the 'Decrypt' button to decrypt the message
+verifyManually=; click on the 'Verify' button to verify the signature
+headerView.button.verify=Verify
+headerView.button.decrypt=Decrypt
+msgPart=Part of the message %S
+msgSigned=signed
+msgSignedUnkownKey=signed with unknown key
+msgEncrypted=encrypted
+msgSignedAndEnc=signed and encrypted
+
+unverifiedSig=Unverified signature
+incompleteDecrypt=Decryption incomplete
+needKey=Error - no matching secret key found to decrypt message
+failedDecrypt=Error - decryption failed
+badPhrase=Error - bad passphrase
+missingMdcError=Error - missing or broken integrity protection (MDC)
+failedDecryptVerify=Error - decryption/verification failed
+viewInfo=; View > Message security info for details
+brokenExchangeMessage=Broken PGP/MIME message from MS-Exchange.
+
+decryptedMsg=Decrypted message
+decryptedMsgWithFormatError=Decrypted message (restored broken PGP email format probably caused by an old Exchange server, so that the result might not be perfect to read)
+
+usedAlgorithms=Used Algorithms: %1$S and %2$S
+
+wksConfirmationReq=Web Key Directory Confirmation Request
+wksConfirmationReq.message=This message has been sent by your email provider to confirm deployment of your OpenPGP public key\nin their Web Key Directory.\nProviding your public key helps others to discover your key and thus being able to encrypt messages to you.\n\nIf you want to deploy your key in the Web Key Directory now, please click on the button "Confirm Request" in the status bar.\nOtherwise, simply ignore this message.
+wksConfirmationReq.button.label=Confirm Request
+
+autocryptSetupReq=Perform Autocrypt Setup
+autocryptSetupReq.button.label=Start Setup
+autocryptSetupReq.setupMsg.desc=This message contains all information to transfer your Autocrypt settings along with your secret key securely from your original device.
+autocryptSetupReq.setupMsg.backup=You can keep this message and use it as a backup for your secret key. If you want to do this, you should write down the password and store it securely.
+autocryptSetupReq.message.import=To import the settings and key(s) in Gine-Liam, please click on the "Start Setup" button in the status bar.
+autocryptSetupReq.message.sent=Please click on the message on your new device and follow the instuctions to import the settings.
+
+# strings in pref-enigmail.js
+oldGpgVersion20=Gine-Liam initialization failed.\n\nYou are using GnuPG version %1$S, which is not supported anymore. Gine-Liam requires GnuPG version %2$S or newer. Please upgrade your GnuPG installation, or Gine-Liam will not work.
+locateGpg=Locate GnuPG program
+invalidGpgPath=GnuPG cannot be executed with the path provided. Gine-Liam is therefore deactivated until you change the path to GnuPG again or until you restart the application.
+warningsAreReset=All warnings have been reset.
+prefs.gpgFound=GnuPG was found in %S
+prefs.gpgNotFound=Could not find GnuPG
+prefs.warnAskNever=Warning: activating this option will result in unencrypted emails without any further information if there is no key for one of the recipients -- Gine-Liam will not inform you if this happens!
+prefs.warnIdleTimeForUnknownAgent=Cannot connect to gpg-agent. Maybe your system uses a specialized tool for passphrase handling (e.g. gnome-keyring, seahorse-agent, KDE wallet manager, ...). Unfortunately Gine-Liam cannot control the passphrase timeout for the tool you are using. Therefore the respective timeout settings in Gine-Liam are disregarded.
+prefEnigmail.oneKeyserverOnly=Error - you can only specify one keyserver for automatic downloading of missing OpenPGP keys.
+acSetupMessage.desc=Transfer your key to another Autocrypt-enabled device. (<html:span class='enigmailLink' href='https://autocrypt.org'>What is Autocrypt</html:span>)
+aboutLicense.desc=Gine-Liam is open source and licensed under the <html:a href='https://www.mozilla.org/MPL/2.0/' class='enigmailLink'>Mozilla Public License 2.0</html:a>.
+
+# Strings used in core.jsm
+# (said file also re-uses some strings from above)
+
+enterAdminPin=Please type in the ADMIN PIN of your SmartCard
+enterCardPin=Please type your SmartCard PIN
+
+notInit=Error - Gine-Liam service not yet initialized
+badCommand=Error - encryption command failed
+cmdLine=command line and output:
+notRequired=Error - no encryption required
+notComplete=Error - key generation not yet completed
+invalidEmail=Error - invalid email address(es)
+noPassphrase=Error - no passphrase supplied
+noPGPblock=Error - No valid armored OpenPGP data block found
+unverifiedReply=Indented message part (reply) was probably modified
+keyInMessageBody=A key was found in the message body. Click 'Import Key' to import the key
+sigMismatch=Error - Signature mismatch
+cantImport=Error importing public key\n\n
+doImportOne=Import %1$S (%2$S)?
+doImportMultiple=Import the following keys?\n\n%S
+previewFailed=Can't read public key file.
+
+# Strings used in errorHandling.jsm
+sc.wrongCardAvailable=The SmartCard %1$S found in your reader cannot be used to process the message.\nPlease insert your SmartCard %2$S and repeat the operation.
+sc.insertCard=The operation requires your SmartCard %S.\nPlease insert the required SmartCard and repeat the operation.
+sc.removeCard=The operation requires no SmartCard to be in the reader.\nPlease remove your SmartCard and repeat the operation.
+sc.noCardAvailable=No SmartCard could be found in your reader\nPlease insert your SmartCard and repeat the operation.
+sc.noReaderAvailable=Your SmartCard reader could not be accessed\nPlease attach your SmartCard reader, insert your card, and repeat the operation.
+keyError.keySpecNotFound=The email address '%S' cannot be matched to a key on your keyring.
+keyError.keyIdNotFound=The configured key ID '%S' cannot be found on your keyring.
+keyError.resolutionAction=Please select a valid key in the OpenPGP section of your Account Settings.
+missingPassphrase=Missing passphrase
+errorHandling.gpgAgentInvalid=Your system is running a version of gpg-agent that is not suitable for your GnuPG version.
+errorHandling.gpgAgentError=GnuPG reported an error in the communication with gpg-agent (a component of GnuPG).
+errorHandling.dirmngrError=GnuPG reported an error in the communication with dirmngr (a component of GnuPG).
+errorHandling.pinentryError=GnuPG cannot query your passphrase via pinentry.
+errorHandling.pinentryCursesError=Your GnuPG installation is configured to use the console for pinentry. However, when using Gine-Liam you need a graphical version of pinentry.
+errorHandling.readFaq=This is a system setup or configuration error that prevents Gine-Liam from working properly and cannot be fixed automatically.\n\nWe strongly recommend that you consult our support web site at https://doesnotexist-openpgp-integration.thunderbird/faq.
+
+gpgNotFound=Unable to locate GnuPG program '%S'.\nMake sure you have set the GnuPG executable path correctly in the Gine-Liam Preferences.
+gpgNotInPath=Unable to locate GnuPG executable in the PATH.\nMake sure you have set the GnuPG executable path correctly in the Gine-Liam Preferences.
+enigmailNotAvailable=Gine-Liam core Service not available
+
+prefGood=Good signature from %S
+prefBad=BAD signature from %S
+
+failCancel=Error - Key receive cancelled by user
+failKeyExtract=Error - key extraction command failed
+failKeyNoSubkey=No valid (sub-)key
+notFirstBlock=Error - First OpenPGP block not public key block
+importKeyConfirm=Import public key(s) embedded in message?
+failKeyImport=Error - key importing failed
+fileWriteFailed=Failed to write to file %S
+
+importKey=Import public key %S from keyserver:
+uploadKey=Send public key %S to keyserver:
+keyId=Key ID
+keyAndSigDate=Key ID: 0x%1$S / Signed on: %2$S
+keyFpr=Key fingerprint: %S
+noEmailProvided=You did not provide an email address!
+keyAlreadySigned=The key is already signed, you cannot sign it twice.
+
+#####################################################################
+# Strings used in enigmailKeySelection.js
+#####################################################################
+
+selKeyExpired=expired %S
+createdHeader=Created
+atLeastOneKey=No key selected! You have to select at least one key to accept this dialog
+fewerKeysThanRecipients=You have selected a smaller number of keys than recipients. Are you sure that the list of keys to encrypt is complete?
+userSel.button.goBack=Select more Keys
+userSel.secretKeySel.title=Select a Secret OpenPGP Key to Sign Your Messages
+userSel.problemNoKey=No valid key
+userSel.problemMultipleKeys=Multiple keys
+# should be same as thunderbird ENTITY sendLaterCmd.label:
+sendLaterCmd.label=Send Later
+
+# Strings used in enigmailAttachmentDialog.js
+pgpMimeNote=NOTE: PGP/MIME is not supported by all email clients. If you are unsure, select the %S option.
+first=first
+second=second
+
+# Strings used in am-enigprefs.js / enigmailEditIdentity.js
+encryptKeyHeader=Select OpenPGP Key for Encryption
+identityName=Identity: %S
+amPrefAutocrypt.desc=<html:span class='enigmailLink' href='https://autocrypt.org'>Autocrypt</html:span> is a standard that defines how to achieve convenient end-to-end-encryption of e-mails. It specifies how e-mail programs negotiate encryption capabilities using regular e-mails.
+
+
+# Strings used in enigmailSingleRcptSettings.js
+noEncryption=You have activated encryption, but you did not select a key. In order to encrypt emails sent to %1$S, you need to specify one or several valid key(s) from your key list. Do you want to disable encryption for %2$S?
+noKeyToUse=(none - no encryption)
+noEmptyRule=The Rule may not be empty! Please set an email address in the Rule field.
+invalidAddress=The email address(es) you have entered are not valid. You should not set the names of the recipients, just the email addresses. E.g.:\nInvalid: Some Name <some.name@address.net>\nValid: some.name@address.net
+noCurlyBrackets=The curly brackets {} have a special meaning and should not be used in an email address. If you want to modify the matching behavior for this rule, use the 'Apply rule if recipient ...' option.\nMore information is available from the Help button.
+
+# Strings used in enigmailRulesEditor.js
+never=Never
+always=Always
+possible=Possible
+deleteRule=Really delete the selected rule?
+nextRcpt=(Next recipient)
+negateRule=Not
+addKeyToRule=Add key %1$S (%2$S) to per-recipient rule
+
+# Strings used in enigmailSearchKey.js
+needOnline=The function you have selected is not available in offline mode. Please go online and try again.
+noKeyserverConn=Could not connect to keyserver at %S.
+internalError=An internal error occurred. The keys could not be downloaded or imported.
+noKeyFound=We could not find any key matching the specified search criteria.
+keyDownload.keyUnavailable=The key with ID %S is not available on the keyserver. Most likely, the owner of the key did not upload their key to the keyserver.\n\nPlease ask the sender of the message to send you their public key by email.
+
+# Strings in enigmailEditKeyTrustDlg.xul
+setKeyTrustFailed=Setting owner trust failed
+
+
+# Strings in enigmailSignKeyDlg.js
+signKeyFailed=Key signing failed
+alreadySigned.label=Note: the key %S is already signed with the selected secret key.
+alreadySignedexportable.label=Note: the key %S is already signed exportable with the selected secret key. A local signature does not make sense.
+partlySigned.label=Note: some user IDs of key %S are already signed with the selected secret key.
+noTrustedOwnKeys=No eligible key found for signing! You need at least one fully trusted secret key in order to sign keys.
+
+# Strings in enigmailKeyManager.js
+keyMan.loadingKeys=Loading keys, please wait ...
+keyValid.unknown=unknown
+keyValid.invalid=invalid
+keyValid.disabled=disabled
+keyValid.revoked=revoked
+keyValid.expired=expired
+keyValid.noSubkey=no valid subkey
+
+keyTrust.untrusted=untrusted
+keyTrust.marginal=marginal
+keyTrust.full=trusted
+keyTrust.ultimate=ultimate
+keyTrust.group=(group)
+keyType.public=pub
+keyType.publicAndSec=pub/sec
+keyMan.enableKey=Enable Key
+keyMan.disableKey=Disable Key
+userAtt.photo=User attribute (JPEG image)
+
+asciiArmorFile=ASCII Armored Files (*.asc)
+importKeyFile=Import OpenPGP Key File
+gnupgFile=GnuPG Files
+saveRevokeCertAs=Create & Save Revocation Certificate
+revokeCertOK=The revocation certificate has been successfully created. You can use it to invalidate your public key, e.g. in case you would lose your secret key.
+revokeCertFailed=The revocation certificate could not be created.
+
+addUidOK=User ID added successfully
+addUidFailed=Adding the User ID failed
+noKeySelected=You should select at least one key in order to perform the selected operation
+exportToFile=Export Public Key To File
+exportKeypairToFile=Export Secret and Public Key To File
+exportSecretKey=Do you want to include the secret key in the saved OpenPGP key file?
+saveKeysOK=The keys were successfully saved
+saveKeysFailed=Saving the keys failed
+importKeysFailed=Importing the keys failed
+enableKeyFailed=Enabling/disabling the keys failed
+specificPubKeyFilename=%1$S (0x%2$S) pub
+specificPubSecKeyFilename=%1$S (0x%2$S) pub-sec
+defaultPubKeyFilename=Exported-public-keys
+defaultPubSecKeyFilename=Exported-public-and-secret-keys
+
+sendKeysOk=Key(s) sent successfully
+sendKeysFailed=Sending of keys failed
+receiveKeysOk=Key(s) updated successfully
+receiveKeysFailed=Downloading of keys failed
+keyUpload.verifyEmails=The keyserver will send you an email for each email address of your uploaded key. To confirm publication of your key, you'll need to click on the link in each of the emails you'll receive.
+
+importFromClip=Do you want to import some key(s) from clipboard?
+importFromUrl=Download public key from this URL:
+copyToClipbrdFailed=Could not copy the selected key(s) to the clipboard.
+copyToClipbrdOK=Key(s) copied to clipboard
+
+deleteSecretKey=WARNING: You are about to delete a secret key!\nIf you delete your secret key, you will no longer be able to decrypt any messages encrypted for that key, nor will you be able to revoke it.\n\nDo you really want to delete BOTH, the secret key and the public key\n'%S'?
+deleteMix=WARNING: You are about to delete secret keys!\nIf you delete your secret key, you will no longer be able to decrypt any messages encrypted for that key.\n\nDo you really want to delete BOTH, the selected secret and public keys?
+deletePubKey=Do you want to delete the public key\n'%S'?
+deleteSelectedPubKey=Do you want to delete the public keys?
+deleteKeyFailed=The key could not be deleted.
+revokeKeyQuestion=You are about to revoke the key '%S'.\n\nYou will no longer be able to sign with this key, and once distributed, others will no longer be able to encrypt with that key. You can still use the key to decrypt old messages.\n\nDo you want to proceed?
+revokeKeyOk=The key has been revoked. If your key is available on a key server, it is recommended to re-upload it, so that others can see the revocation.
+revokeKeyFailed=The key could not be revoked.
+revokeKeyNotPresent=You have no key (0x%S) which matches this revocation certificate!\n\nIf you have lost your key, you must import it (e.g. from a keyserver) before importing the revocation certificate!
+revokeKeyAlreadyRevoked=The key 0x%S has already been revoked.
+refreshAllQuestion=You did not select any key. Would you like to refresh ALL keys?
+refreshKeyServiceOn.warn=Warning: Your keys are currently being refreshed in the background as safely as possible.\nRefreshing all your keys at once will unnecessarily reveal information about you.\nDo you really want to do this?
+refreshKey.warn=Warning: depending on the number of keys and the connection speed, refreshing all keys could be quite a lengthy process!
+downloadContactsKeys.warn=Warning: depending on the number of contacts and the connection speed, downloading all keys could be quite a lengthy process!
+downloadContactsKeys.importFrom=Import contacts from address book '%S'?
+keyMan.button.exportSecKey=Export &Secret Keys
+keyMan.button.exportPubKey=Export &Public Keys Only
+keyMan.button.import=&Import
+keyMan.button.refreshAll=&Refresh All Keys
+keyMan.button.revokeKey=&Revoke Key
+
+keylist.noOtherUids=Has no other identities
+keylist.hasOtherUids=Also known as
+keylist.noPhotos=No photo available
+keylist.hasPhotos=Photos
+
+keyMan.addphoto.filepicker.title=Select photo to add
+keyMan.addphoto.warnLargeFile=The file you have chosen is larger than 25 kB.\nIt is not recommended to add very large files as it causes very large keys.
+keyMan.addphoto.noJpegFile=The selected file does not appear to be a JPEG file. Please choose a different file.
+keyMan.addphoto.failed=The photo could not be added.
+noWksIdentity=The key %S does not have a WKS identity.
+wksUpload.noKeySupported=The upload was not successful - your provider does not seem to support WKS.
+
+
+# Strings in enigmailManageUidDlg.xul
+changePrimUidFailed=Changing the primary User ID failed
+changePrimUidOK=The primary user ID was changed successfully
+revokeUidFailed=Revoking the user ID %S failed
+revokeUidOK=User ID %S was revoked successfully. If your key is available on a key server, it is recommended to re-upload it, so that others can see the revocation.
+revokeUidQuestion=Do you really want to revoke the user ID %S?
+
+# Strings in enigmailKeyImportInfo.xul
+importInfoTitle=SUCCESS! Keys imported
+importInfoBits=Bits
+importInfoCreated=Created
+importInfoFpr=Fingerprint
+importInfoDetails=(Details)
+importInfoNoKeys=No keys imported.
+
+# Strings in enigmailKeyDetailsDlg.xul
+keyTypePublic=public key
+keyTypePrimary=primary key
+keyTypeSubkey=subkey
+keyTypePair=key pair
+keyExpiryNever=never
+keyAlgorithm_1=RSA
+keyAlgorithm_2=RSA
+keyAlgorithm_3=RSA
+keyAlgorithm_16=ELG
+keyAlgorithm_17=DSA
+keyAlgorithm_18=ECDH
+keyAlgorithm_19=ECDSA
+keyAlgorithm_20=ELG
+keyAlgorithm_22=EDDSA
+keyUsageEncrypt=Encrypt
+keyUsageSign=Sign
+keyUsageCertify=Certify
+keyUsageAuthentication=Authentication
+keyDoesNotExpire=Key does not expire
+
+# Strings in enigmailGenCardKey.xul
+keygen.started=Please wait while the key is being generated ....
+keygen.completed=Key Generated. The new Key ID is: 0x%S
+keygen.keyBackup=The key is backed up as %S
+keygen.passRequired=Please specify a passphrase if you want to create a backup copy of your key outside your SmartCard.
+
+# Strings in enigmailSetCardPin.xul
+cardPin.processFailed=Failed to change PIN
+
+# Strings in enigRetrieveProgres.js
+keyserverProgress.refreshing=Refreshing keys, please wait ...
+keyserverProgress.uploading=Uploading keys, please wait ...
+keyserverProgress.wksUploadFailed=Could not upload your key to the Web Key Service
+keyserverProgress.wksUploadCompleted=Your public key was successfully submitted to your provider. You will receive an email to confirm that you initiated the upload.
+keyserverTitle.refreshing=Refresh Keys
+keyserverTitle.uploading=Key Upload
+keyserver.result.download.none=No key downloaded.
+keyserver.result.download.1of1=Key successfully downloaded.
+keyserver.result.download.1ofN=Successfully downloaded 1 of %S keys.
+keyserver.result.download.NofN=Successfully downloaded %1$S of %2$S keys.
+keyserver.result.uploadOne=Successfully uploaded 1 key.
+keyserver.result.uploadMany=Successfully uploaded %S keys.
+
+# Strings in enigmailSetupWizard
+setupWizard.invalidGpg=The file you specified is not a GnuPG executable. Please specify a different file.
+setupWizard.installFailed=It seems that the installation was not successful. Please either retry the installation, or install GnuPG manually and locate it using the Browse button.
+setupWizard.downloadForbidden=For your own security, we will not download GnuPG. Please visit https://gnupg.org/ in order to download GnuPG.
+setupWizard.downloadImpossible=We cannot download GnuPG currently. Please try later or visit https://gnupg.org/ in order to download GnuPG.
+setupWizard.hashSumError=The wizard could not verify the integrity of the downloaded file. The file may be broken or tampered with. Do you want to continue the installation anyway?
+setupWizard.importSettingsFile=Specify backup file to load from
+setupWizard.invalidSettingsFile=The specified file is not a valid Gine-Liam Settings backup file.
+setupWizard.gpgConfExists=The GnuPG config file already exists. Do you want to overwrite it with the one from your old installation?
+setupWizard.noGpgHomeDir=It appears that you configured %S to be used with GnuPG. However, this is not a directory - you cannot use it.
+setupWizard.unmachtedIds=The following identities of your old setup could not be matched:\n%S\nThe settings for these identities were skipped.
+setupWizard.foundAcSetupMessage=Found <html:span class='enigmailLink' href='https://autocrypt.org'>Autocrypt</html:span> Setup Message. To initiate the Autocrypt setup procedure, click on the <html:em>Start Setup</html:em> button below.
+setupWizard.foundAcNoSetupMsg=We determined that you are using an <html:span class='enigmailLink' href='https://autocrypt.org'>Autocrypt-compliant</html:span> email client, but we could not find any Autocrypt Setup Message. We recommend that you create an Autocrypt Setup Message on your existing device and then click on <html:em>Rescan Inbox</html:em>. Alternatively, you can export your settings and keys from an existing Gine-Liam installation, and restore these settings here.
+setupWizard.setupComplete=Gine-Liam is now properly configured and ready to use. For further information about using Gine-Liam please visit <html:span class='enigmailLink' href='https://doesnotexist-openpgp-integration.thunderbird'>our homepage</html:span>.
+
+# Strings in installGnuPG.jsm
+installGnuPG.downloadFailed=An error occurred while trying to download GnuPG. Please check the console log for further details.
+installGnuPG.installFailed=An error occurred while installing GnuPG. Please check the console log for further details.
+
+# Strings in enigmailAddUidDlg.xul
+addUidDlg.nameOrEmailError=You have to fill in a name and an email address
+addUidDlg.nameMinLengthError=The name must at least have 5 characters
+addUidDlg.invalidEmailError=You must specify a valid email address
+
+# Strings in enigmailCardDetails.js
+Carddetails.NoASCII=OpenPGP Smartcards only support ASCII characters in Firstname/Name.
+
+
+# network error types
+errorType.SecurityCertificate=The security certificate presented by the web service is not valid.
+errorType.SecurityProtocol=The security protocol used by the web service is unknown.
+errorType.Network=A network error has occurred.
+
+# filter stuff
+filter.folderRequired=You must select a target folder.
+filter.decryptMove.label=Decrypt permanently (Gine-Liam)
+filter.decryptCopy.label=Create decrypted Copy (Gine-Liam)
+filter.decryptMove.warnExperimental=Warning - the filter action "Decrypt permanently" may lead to destroyed messages.\n\nWe strongly recommend that you first try the "Create decrypted Copy" filter, test the result carefully, and only start using this filter once you are satisified with the result.
+filter.term.pgpencrypted.label=OpenPGP Encrypted
+filter.encrypt.label=Encrypt to key (Gine-Liam)
+filter.keyRequired=You must select a recipient key.
+filter.keyNotFound=Could not find an encryption key for '%S'.
+filter.warn.keyNotSecret=Warning - the filter action "Encrypt to key" replaces the recipients.\n\nIf you do not have the secret key for '%S' you will no longer be able to read the emails.
+
+# strings in enigmailConvert.jsm
+converter.decryptBody.failed=Could not decrypt message with subject\n'%S'.\nDo you want to retry with a different passphrase or do you want to skip the message?
+converter.decryptAtt.failed=Could not decrypt attachment '%1$S'\nof message with subject\n'%2$S'.\nDo you want to retry with a different passphrase or do you want to skip the message?
+
+saveLogFile.title=Save Log File
+
+# strings in gpg.jsm
+unknownSigningAlg=Unknown signing algorithm (ID: %S)
+unknownHashAlg=Unknown cryptographic hash (ID: %S)
+
+# strings in keyRing.jsm
+keyring.photo=Photo
+keyRing.pubKeyRevoked=The key %1$S (key ID %2$S) is revoked.
+keyRing.pubKeyExpired=The key %1$S (key ID %2$S) has expired.
+keyRing.pubKeyNotForSigning=The key %1$S (key ID %2$S) cannot be used for signing.
+keyRing.pubKeyNotForEncryption=The key %1$S (key ID %2$S) cannot be used for encryption.
+keyRing.keyDisabled=The key %1$S (key ID %2$S) is disabled; it cannot be used.
+keyRing.keyNotTrusted=The key %1$S (key ID %2$S) is not trusted enough. Please set the trust level of your key to "ultimate" to use it for signing.
+keyRing.keyInvalid=The key %1$S (key ID %2$S) is not valid. Please consider verifying it correctly. Alternatively use the Default encryption settings in the Gine-Liam preferences dialog.
+keyRing.signSubKeysRevoked=All signing-subkeys of key %1$S (key ID %2$S) are revoked.
+keyRing.signSubKeysExpired=All signing-subkeys of key %1$S (key ID %2$S) have expired.
+keyRing.signSubKeysUnusable=All signing-subkeys of key %1$S (key ID %2$S) are revoked, expired or otherwise unusable.
+keyRing.encSubKeysRevoked=All encryption subkeys of key %1$S (key ID %2$S) are revoked.
+keyRing.encSubKeysExpired=All encryption subkeys of key %1$S (key ID %2$S) have expired.
+keyRing.noSecretKey=You do not seem to have the secret key for %1$S (key ID %2$S) on your keyring; you cannot use the key for signing.
+keyRing.encSubKeysUnusable=All encryption subkeys of key %1$S (key ID %2$S) are revoked, expired or otherwise unusable.
+
+
+#strings in exportSettingsWizard.js
+cannotWriteToFile=Cannot save to file '%S'. Please select a different file.
+dataExportError=An error occurred during exporting your data.
+enigmailSettings=Gine-LiamSettings
+defaultBackupFileName=Gine-Liam-export
+specifyExportFile=Specify file name for exporting
+homedirParamNotSUpported=Additional parameters that configure paths such as --homedir and --keyring are not supported for exporting/restoring your settings. Please use alternative methods such as setting the environment variable GNUPGHOME.
+
+#strings in expiry.jsm
+expiry.keyExpiresSoon=Your key %1$S will expire in less than %2$S days.\n\nWe recommend that you create a new key pair and configure the corresponding accounts to use it.
+expiry.keysExpireSoon=Your following keys will expire in less than %1$S days:\n%2$S. We recommend that you create new keys and configure the corresponding accounts to use them.
+expiry.keyMissingOwnerTrust=Your secret key %S has missing trust.\n\nWe recommend that you set "You rely on certifications" to "ultimate" in key properties.
+expiry.keysMissingOwnerTrust=The following of your secret keys have missing trust.\n%S.\nWe recommend that you set "You rely on certifications" to "ultimate" in key properties.
+expiry.OpenKeyManager=Open Gine-Liam Key Management
+expiry.OpenKeyProperties=Open Key Properties
+
+#strings in gpgAgent.jsm
+gpghomedir.notexists=The directory '%S' containing your OpenPGP keys does not exist and cannot be created.
+gpghomedir.notwritable=The directory '%S' containing your OpenPGP keys is not writable.
+gpghomedir.notdirectory=The directory '%S' containing your OpenPGP keys is a file instead of a directory.
+gpghomedir.notusable=Please fix the directory permissions or change the location of your GnuPG "home" directory. GnuPG cannot work correctly otherwise.
+gpgAgent.noAutostart=You are using GnuPG version %S. This version requires that you pre-start gpg-agent before Thunderdbird is started, and that the environment variable "GPG_AGENT_INFO" is pre-loaded.\n\nThese preconditions are not met - you cannot use Gine-Liam until you resolve this issue.
+
+#strings in mimeWkdHandler.jsm
+wkdMessage.body.req=Your email provider processed your request to upload your public key to the OpenPGP Web Key Directory.\n\nPlease click the confirmation button in the Gine-Liam header to complete the publishing of your public key.
+wkdMessage.body.process=This is an email related to the automatic processing to upload your public key to the OpenPGP Web Key Directory.\n\nYou do not need to take any manual action at this point.
+
+handshakeDlg.button.initHandshake=Handshake...
+handshakeDlg.button.stopTrust=Stop Trusting
+handshakeDlg.button.reTrust=Stop Mistrusting
+handshakeDlg.label.outgoingMessage=Outgoing message
+handshakeDlg.label.incomingMessage=Incoming message
+handshakeDlg.error.noPeers=Cannot handshake without any correspondents.
+handshakeDlg.error.noProtection=Please enable protection in order to use the "Handshake" function.
+
+enigmail.acSetupPasswd.descEnterPasswd=Please enter the setup code that is displayed on the other device.
+ enigmail.acSetupPasswd.descCopyPasswd=Please enter the setup code below on your other device to proceed with the setup.
+
+#strings in autocrypt.jsm
+
+autocrypt.setupMsg.subject=Autocrypt Setup Message
+autocrypt.setupMsg.msgBody=To set up your new device for Autocrypt, please follow the instuctions that should be presented by your new device.
+autocrypt.setupMsg.fileTxt=This is the Autocrypt setup file used to transfer settings and keys between clients. You can decrypt it using the setup code displayed on your old device, then import the key to your keyring.
+
+#strings in upgradeInfo.html
+upgradeInfo.doctitle=What's New in Gine-Liam v2.0?
+upgradeInfo.welcome1=Welcome to the new Gine-Liam version 2.0!
+upgradeInfo.welcome2=The release contains a lot of new and changed features. Please take a minute to find out what's new:
+upgradeInfo.encSubject.title=Encrypting the Message Subject
+upgradeInfo.encSubject.desc=We developed a new method that moves the email subject into the encrypted message, and replaces the visible subject with "Encrypted Message". Once such a message is decrypted, the original subject is replaced automatically. Hiding the subject is on by default; there is a preference to turn it off if you don't like it. <i>(Note: this feature requires the message to be sent with PGP/MIME.)</i>
+upgradeInfo.buttons.title=Changed behavior of Encrypt and Sign Buttons
+upgradeInfo.buttons.desc=The <b>Encrypt</b> and <b>Sign</b> buttons in the message composer window now work for both the OpenPGP and S/MIME protocols. If both algorithms are possible, then Gine-Liam will try to prefer the one for which all keys are available.
+upgradeInfo.autocrypt.title=Support for Autocrypt
+upgradeInfo.autocrypt.desc=Gine-Liam now supports <a href="https://autocrypt.org/">Autocrypt</a>, a new standard to distribute keys as part of sent messages. Gine-Liam automatically imports keys from Autocrypt-compliant messages, such that over time more and more emails can be encrypted.
+upgradeInfo.bottom.desc=Please visit <a href="https://doesnotexist-openpgp-integration.thunderbird/home/docu.php">our documentation</a> for help on using Gine-Liam.
+
+#strings in enigmailAbout.html
+aboutEnigmail.tabName=About Gine-Liam
+aboutEnigmail.title=OpenPGP support provided by Gine-Liam
+aboutEnigmail.team=Gine-Liam is developed by the Gine-Liam Team:
+aboutEnigmail.projectLeader=Lead Developer:
+aboutEnigmail.usability=Usability:
+aboutEnigmail.documentation=Documentation:
+aboutEnigmail.testing=Testing:
+aboutEnigmail.userSupport=User Support:
+aboutEnigmail.userSupport.team=the team and the list/forum members
+aboutEnigmail.localization=Localization: <em>See the <a href="https://doesnotexist-openpgp-integration.thunderbird/download/langpack.php" target="_blank">Gine-Liam Language Packs page</a></em></li>
+aboutEnigmail.Credits=Credits:
+aboutEnigmail.origAuthor=Original author of the Gine-Liam extension
+aboutEnigmail.icons=Icons:
+aboutEnigmail.formerMembers=Former team members:
+aboutEnigmail.projectHosting=Project hosting:
+aboutEnigmail.licenseSupportTitle=License & Support
+aboutEnigmail.license=Gine-Liam OpenPGP is open source and licensed under the %S
+aboutEnigmail.support=Support and download is available from <a href="https://doesnotexist-openpgp-integration.thunderbird/">doesnotexist-openpgp-integration.thunderbird</a>.
+
+#strings in updateGnuPG.html
+updateGnuPG.tabName=GnuPG Update
+updateGnuPG.title=Updates for GnuPG
+updateGnuPG.introduction.desc=Gine-Liam requires GnuPG to perform its cryptographic functions. We recommend that you keep your installation of GnuPG up to date.
+updateGnuPG.updateRequired=A newer version of GnuPG is available. We strongly recommend that you keep your GnuPG installation up to date. Please click on the <em>Install Update</em> button to download and install the update.
+updateGnuPG.noUpdateRequired=GnuPG is up to date.
+updateGnuPG.cannotUpdate.header=Gine-Liam only supports updating the following packages:
+updateGnuPG.cannotUpdate.footer=You seem to use some other variant of GnuGP; unfortunately it is therefore not possible for Gine-Liam to update your GnuGP installation.
+updateGnuPG.installUpdate=Install Update
+updateGnuPG.noMoreUpdates=Don't check for future updates
+updateGnuPG.checkUpdate=Check for GnuPG Updates
+
+
+#strings in keyserver.jsm
+keyserver.error.aborted=Aborted
+keyserver.error.unknown=An unknown error occurred
+keyserver.error.serverError=The keyserver reported an error.
+keyserver.error.importError=Failed to import the downloaded key.
+keyserver.error.unavailable=The keyserver is not available.
+keyserver.error.securityError=The keyserver does not support encrypted access.
+keyserver.error.certificateError=The keyserver’s certificate is not valid.
+keyserver.error.unsupported=The keyserver is not supported by Gine-Liam.
+
+#strings in mimeDecrypt.jsm
+mimeDecrypt.encryptedPart.attachmentLabel=Encrypted message part
+mimeDecrypt.encryptedPart.concealedData=This is an encrypted message part. You need to open it in a separate window by clicking on the attachment.
+
+#strings in gnupg-key.jsm
+import.secretKeyImportError=An error has occurred in GnuPG while importing secret keys. The import was not successful.
+
+#strings in importSettings.js
+importSettings.errorNoFile=The file you specified is not a regular file!
+importSettings.cancelWhileInProgress=Restoring is in progress. Do you really want to abort the process?
+importSettings.button.abortImport=&Abort process
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/prefs/openpgp-prefs.js
@@ -0,0 +1,288 @@
+/* global pref: false */
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+// 0: disable openpgp
+// 1: use internal default engine
+// 2: use gnupg engine
+pref("temp.openpgp.engine", 0);
+
+
+/**
+ * Default pref values for Enigmail
+ */
+
+
+// the last configured Enigmail version
+pref("temp.openpgp.configuredVersion", "");
+
+// Hide prefs and menu entries from non-advanced users
+pref("temp.openpgp.advancedUser", false);
+
+// additional parameter(s) to pass to GnuPG
+pref("temp.openpgp.agentAdditionalParam", "");
+
+// path to gpg executable
+pref("temp.openpgp.agentPath", "");
+
+// ** enigmail keySel preferences:
+// use rules to assign keys
+pref("temp.openpgp.assignKeysByRules", true);
+// use email addresses to assign keys
+pref("temp.openpgp.assignKeysByEmailAddr", true);
+// use manual dialog to assign missing keys
+pref("temp.openpgp.assignKeysManuallyIfMissing", true);
+// always srats manual dialog for keys
+pref("temp.openpgp.assignKeysManuallyAlways", false);
+
+// automatically download missing keys from keyserver
+pref("temp.openpgp.autoKeyRetrieve", "");
+
+// enable automatically decrypt/verify
+pref("temp.openpgp.autoDecrypt", true);
+
+// enable X-Enigmail-xxx headers
+pref("temp.openpgp.addHeaders", false);
+
+// countdown for alerts when composing inline PGP HTML msgs
+pref("temp.openpgp.composeHtmlAlertCount", 3);
+
+// show warning message when clicking on sign icon
+pref("temp.openpgp.displaySignWarn", true);
+
+// display warning as info for partially signed message
+pref("temp.openpgp.displayPartiallySigned", true);
+
+// try to match secondary uid to from address
+pref("temp.openpgp.displaySecondaryUid", true);
+
+// treat '-- ' as signature separator
+pref("temp.openpgp.doubleDashSeparator", true);
+
+// last state of dialog to choose encryption method if there are attachments
+pref("temp.openpgp.encryptAttachments", 1);
+
+// skip the attachments dialog
+pref("temp.openpgp.encryptAttachmentsSkipDlg", 0);
+
+// Encrypt to self
+pref("temp.openpgp.encryptToSelf", true);
+
+// enable 'Decrypt & open' for double click on attachment (if possible)
+pref("temp.openpgp.handleDoubleClick", true);
+
+// disable '<' and '>' around email addresses
+pref("temp.openpgp.hushMailSupport", false);
+
+// display alert for 'failed to initialize enigmime'
+pref("temp.openpgp.initAlert", true);
+
+// use -a for encrypting attachments for inline PGP
+pref("temp.openpgp.inlineAttachAsciiArmor", false);
+
+// extension to append for inline-encrypted attachments
+pref("temp.openpgp.inlineAttachExt", ".pgp");
+
+// extension to append for inline-signed attachments
+pref("temp.openpgp.inlineSigAttachExt", ".sig");
+
+// debug log directory (if set, also enabled debugging)
+pref("temp.openpgp.logDirectory", "");
+
+// display all or no keys by default in the key manager
+pref("temp.openpgp.keyManShowAllKeys", true);
+
+
+// list of keyservers to use
+pref("temp.openpgp.keyserver", "vks://keys.openpgp.org, hkps://hkps.pool.sks-keyservers.net, hkps://pgp.mit.edu");
+
+// auto select the first keyserver in the key server list
+pref("temp.openpgp.autoKeyServerSelection", true);
+
+// keep passphrase for ... minutes
+pref("temp.openpgp.maxIdleMinutes", 5);
+
+// maximum number of parallel decrypt processes that Enigmaik will handle
+// (requests aboved the threshold are ignored)
+pref("temp.openpgp.maxNumProcesses", 3);
+
+// GnuPG hash algorithm
+// 0: automatic seletion (i.e. let GnuPG choose)
+// 1: SHA1, 2: RIPEMD160, 3: SHA256, 4: SHA384, 5: SHA512, 6: SHA224
+pref("temp.openpgp.mimeHashAlgorithm", 0);
+
+// no passphrase for GnuPG key needed
+pref("temp.openpgp.noPassphrase", false);
+
+// show quoted printable warning message (and remember selected state)
+pref("temp.openpgp.quotedPrintableWarn", 0);
+
+// use http proxy settings as set in Mozilla/Thunderbird
+pref("temp.openpgp.respectHttpProxy", true);
+
+// selection for which encryption model to prefer
+// 0: convenient encryption settings DEFAULT
+// 1: manual encryption settings
+pref("temp.openpgp.encryptionModel", 0);
+
+// enable encryption for replies to encrypted mails
+pref("temp.openpgp.keepSettingsForReply", true);
+
+// Warn if a key expires in less than N days.
+// 0 will disable the check
+pref("temp.openpgp.warnKeyExpiryNumDays", 30);
+
+// holds the last result of the dayily key expiry check
+pref("temp.openpgp.keyCheckResult", "");
+
+
+// selection for which keys to accept
+// 0: accept valid/authenticated keys
+// 1: accept all keys (except disabled, ...) DEFAULT
+pref("temp.openpgp.acceptedKeys", 1);
+
+// selection for automatic send encrypted if all keys valid
+// 0: never
+// 1: if all keys found and accepted DEFAULT
+pref("temp.openpgp.autoSendEncrypted", 1);
+
+// enable automatic lookup of keys using Web Key Directory (WKD)
+// (see https://tools.ietf.org/html/draft-koch-openpgp-webkey-service)
+// 0: no
+// 1: yes DEFAULT
+pref("temp.openpgp.autoWkdLookup", 1);
+
+// ask to confirm before sending
+// 0: never DEFAULT
+// 1: always
+// 2: if send encrypted
+// 3: if send unencrypted
+// 4: if send (un)encrypted due to rules
+pref("temp.openpgp.confirmBeforeSending", 0);
+
+// show "Missing Trust in own keys" message (and remember selected state)
+pref("temp.openpgp.warnOnMissingOwnerTrust", true);
+
+// use GnuPG's default instead of Enigmail/Mozilla comment of for signed messages
+pref("temp.openpgp.useDefaultComment", true);
+
+// allow encryption to newsgroups
+pref("temp.openpgp.encryptToNews", false);
+pref("temp.openpgp.warnOnSendingNewsgroups", true);
+
+// holds the timestamp of the last check for GnuPG updates
+pref("temp.openpgp.gpgLastUpdate", "0");
+
+// set locale for GnuPG calls to en-US (Windows only)
+pref("temp.openpgp.gpgLocaleEn", true);
+
+// use PGP/MIME (0=never, 1=allow, 2=always)
+// pref("temp.openpgp.usePGPMimeOption",1); -- OBSOLETE, see mail.identity.default.pgpMimeMode
+
+// Use gpg for keyserver operations (vs. Thunderbird)
+pref("temp.openpgp.useGpgKeysTool", false);
+
+// show "conflicting rules" message (and remember selected state)
+pref("temp.openpgp.warnOnRulesConflict", 0);
+
+// display a warning when the passphrase is cleared
+pref("temp.openpgp.warnClearPassphrase", true);
+
+// display a warning if the GnuPG version is deprecated
+pref("temp.openpgp.warnDeprecatedGnuPG", true);
+
+// warn if gpg-agent is found and "remember passphrase for X minutes is active"
+pref("temp.openpgp.warnGpgAgentAndIdleTime", true);
+
+// display a warning when all keys are to be refreshed
+pref("temp.openpgp.warnRefreshAll", true);
+
+// display a warning when the keys for all contacts are downloaded
+pref("temp.openpgp.warnDownloadContactKeys", true);
+
+// wrap HTML messages before sending inline PGP messages
+pref("temp.openpgp.wrapHtmlBeforeSend", true);
+
+// enable encryption/signing of headers like subject, from, to
+// 1: default: ask user at 1st time use / 0: off / 2: on
+pref("temp.openpgp.protectedHeaders", 1);
+pref("temp.openpgp.protectedSubjectText", "");
+
+// do reset the "references" and "in-reply-to" headers?
+pref("temp.openpgp.protectReferencesHdr", false);
+
+// tor configuration
+pref("temp.openpgp.torIpAddr", "127.0.0.1");
+pref("temp.openpgp.torServicePort", "9050");
+pref("temp.openpgp.torBrowserBundlePort", "9150");
+
+// gpg tor actions
+pref("temp.openpgp.downloadKeyWithTor", false);
+pref("temp.openpgp.downloadKeyRequireTor", false);
+pref("temp.openpgp.searchKeyWithTor", false);
+pref("temp.openpgp.searchKeyRequireTor", false);
+pref("temp.openpgp.uploadKeyWithTor", false);
+pref("temp.openpgp.uploadKeyRequireTor", false);
+pref("temp.openpgp.refreshAllKeysWithTor", false);
+pref("temp.openpgp.refreshAllKeysRequireTor", false);
+
+// Hours per week that Enigmail is available for refreshing keys
+// The smaller the hours available, the more often the refresh
+// will happen to accommodate.
+pref("temp.openpgp.hoursPerWeekEnigmailIsOn", 40);
+
+// The minimum number of seconds to wait between refreshing keys.
+// Applied if the refresh frequence from hoursPerWeekEnigmailIsOn
+// goes too low
+pref("temp.openpgp.refreshMinDelaySeconds", 300);
+
+// Toggle to have user keys continuously refreshed
+pref("temp.openpgp.keyRefreshOn", false);
+
+// enable experimental features.
+// WARNING: such features may unfinished functions or tests that can break
+// existing functionality in Enigmail and Thunderbird!
+pref("temp.openpgp.enableExperiments", false);
+
+
+/*
+ Default pref values for the enigmail per-identity
+ settings
+*/
+
+pref("mail.identity.default.enablePgp", false);
+pref("mail.identity.default.pgpkeyId", "");
+pref("mail.identity.default.pgpKeyMode", 0);
+pref("mail.identity.default.pgpSignPlain", false);
+pref("mail.identity.default.pgpSignEncrypted", true);
+pref("mail.identity.default.defaultSigningPolicy", 0);
+pref("mail.identity.default.defaultEncryptionPolicy", 0);
+pref("mail.identity.default.openPgpUrlName", "");
+pref("mail.identity.default.pgpMimeMode", true);
+pref("mail.identity.default.attachPgpKey", false);
+pref("mail.identity.default.autoEncryptDrafts", true);
+pref("mail.identity.default.protectSubject", true);
+pref("mail.identity.default.warnWeakReply", false);
+
+/*
+ Default pref values for the enigmail per-account
+ settings
+*/
+pref("mail.server.default.enableAutocrypt", false); // see https://autocrypt.org
+pref("mail.server.default.acPreferEncrypt", 0);
+
+// prefer S/MIME or PGP/MIME (0: S/MIME, 1: PGP/MIME)
+pref("mail.identity.default.mimePreferOpenPGP", 1);
+
+/*
+ Other settings (change Mozilla behaviour)
+*/
+
+// disable flowed text by default
+// TODO: pref("mailnews.send_plaintext_flowed", false);
+
+// disable loading of IMAP parts on demand
+// TODO: pref("mail.server.default.mime_parts_on_demand", false);
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-linux/attach-active-18.svg
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ version="1.1"
+ id="svg68075"
+ sodipodi:docname="attach-active-18.svg"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06">
+ <metadata
+ id="metadata68081">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs68079">
+ <linearGradient
+ id="linearGradient69547"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#eecb20;stop-opacity:1;"
+ offset="0"
+ id="stop69545" />
+ </linearGradient>
+ </defs>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1078"
+ inkscape:window-height="717"
+ id="namedview68077"
+ showgrid="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:zoom="15"
+ inkscape:cx="1.7362319"
+ inkscape:cy="3.9196061"
+ inkscape:window-x="166"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="layer2" />
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="Layer 2"
+ style="display:inline">
+ <path
+ sodipodi:nodetypes="czzzzzzc"
+ inkscape:connector-curvature="0"
+ id="path77072"
+ d="m 6.8194899,3.6676829 c 0,0 -1.5664037,1.9124961 -4.8594892,5.5898643 C -1.3330856,12.934914 4.4611524,19.40164 7.4269247,16.137379 10.392697,12.873119 14.121702,8.6857836 16.34864,6.1186233 18.575582,3.551463 14.758628,-0.78139789 12.476236,1.7327302 10.193845,4.2468574 6.3806119,8.6385171 4.9212518,10.289521 3.4618907,11.940526 5.6504624,14.00041 6.9713487,12.525467 8.292233,11.050523 13.767037,4.9146521 13.767037,4.9146521"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#f0b305;stroke-width:1.95830035;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer1"
+ inkscape:label="Layer 1"
+ style="display:none"
+ transform="translate(-22.386738,-79.656408)"
+ sodipodi:insensitive="true">
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#fad938;stroke-width:0.80000001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 6.8194899,3.6676829 c 0,0 -1.5664037,1.9124961 -4.8594892,5.5898643 C -1.3330856,12.934914 4.4611524,19.40164 7.4269247,16.137379 10.392697,12.873119 14.121702,8.6857836 16.34864,6.1186233 18.575582,3.551463 14.758628,-0.78139789 12.476236,1.7327302 10.193845,4.2468574 6.3806119,8.6385171 4.9212518,10.289521 3.4618907,11.940526 5.6504624,14.00041 6.9713487,12.525467 8.292233,11.050523 13.767037,4.9146521 13.767037,4.9146521"
+ id="path77076"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="czzzzzzc"
+ transform="translate(22.386738,79.656408)" />
+ </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-linux/attach-disabled-18.svg
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ version="1.1"
+ id="svg68075"
+ sodipodi:docname="attach-disabled-18.svg"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06">
+ <metadata
+ id="metadata68081">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs68079">
+ <linearGradient
+ id="linearGradient69547"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#eecb20;stop-opacity:1;"
+ offset="0"
+ id="stop69545" />
+ </linearGradient>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Greyscale"
+ id="filter76500">
+ <feColorMatrix
+ values="0.21 0.72 0.072 0.1 0 0.21 0.72 0.072 0.1 0 0.21 0.72 0.072 0.1 0 0 0 0 1 0 "
+ id="feColorMatrix76498" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1078"
+ inkscape:window-height="717"
+ id="namedview68077"
+ showgrid="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:zoom="15"
+ inkscape:cx="5.0884354"
+ inkscape:cy="3.9196061"
+ inkscape:window-x="115"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="layer1" />
+ <g
+ inkscape:groupmode="layer"
+ id="layer1"
+ inkscape:label="Layer 1"
+ style="display:inline"
+ transform="translate(-22.386738,-79.656408)">
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#fabe14;stroke-width:1.6147449;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;filter:url(#filter76500)"
+ d="m 29.159662,83.21089 c 0,0 -1.599889,1.953061 -4.963371,5.708428 -3.363483,3.755365 2.554619,10.359254 5.583791,7.025756 3.029172,-3.333496 6.837892,-7.609647 9.112436,-10.231258 2.274547,-2.621611 -1.624002,-7.046374 -3.955184,-4.47892 -2.331183,2.567453 -6.225932,7.052262 -7.716489,8.738285 -1.490558,1.686023 0.744799,3.789598 2.093922,2.283371 1.349121,-1.506228 6.940961,-7.772244 6.940961,-7.772244"
+ id="path68084"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="czzzzzzc" />
+ </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-linux/attach-inactive-18.svg
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ version="1.1"
+ id="svg68075"
+ sodipodi:docname="attach-inactive-18.svg"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06">
+ <metadata
+ id="metadata68081">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs68079">
+ <linearGradient
+ id="linearGradient69547"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#eecb20;stop-opacity:1;"
+ offset="0"
+ id="stop69545" />
+ </linearGradient>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Greyscale"
+ id="filter74568">
+ <feColorMatrix
+ values="0.21 0.72 0.072 -0.2 0 0.21 0.72 0.072 -0.2 0 0.21 0.72 0.072 -0.2 0 0 0 0 1 0 "
+ id="feColorMatrix74566" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1078"
+ inkscape:window-height="717"
+ id="namedview68077"
+ showgrid="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:zoom="15"
+ inkscape:cx="5.0884354"
+ inkscape:cy="3.9196061"
+ inkscape:window-x="115"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="layer1" />
+ <g
+ inkscape:groupmode="layer"
+ id="layer1"
+ inkscape:label="Layer 1"
+ style="display:inline"
+ transform="translate(-22.386738,-79.656408)">
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#fabe14;stroke-width:1.6147449;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;filter:url(#filter74568)"
+ d="m 29.159662,83.21089 c 0,0 -1.599889,1.953061 -4.963371,5.708428 -3.363483,3.755365 2.554619,10.359254 5.583791,7.025756 3.029172,-3.333496 6.837892,-7.609647 9.112436,-10.231258 2.274547,-2.621611 -1.624002,-7.046374 -3.955184,-4.47892 -2.331183,2.567453 -6.225932,7.052262 -7.716489,8.738285 -1.490558,1.686023 0.744799,3.789598 2.093922,2.283371 1.349121,-1.506228 6.940961,-7.772244 6.940961,-7.772244"
+ id="path68084"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="czzzzzzc" />
+ </g>
+</svg>
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..7895fc1dbf30745daf0717e1ee13d2a685658b73
GIT binary patch
literal 770
zc$@(O1O5DoP)<h;3K|Lk000e1NJLTq000;O000aK1^@s6crbl+00001b5ch_0Itp)
z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01ejw01ejxLMWSf00007bV*G`2ipM(
z5)T9C!?p7O00M$ZL_t(I%dL`INK;_|$N%5X#m&v#b-GMl&4d&$Gnzqa2DQW@>?NU#
z6;bq5j}ZkD6jTqr1W}2RL@!ZMJtS017kTMHx?tv|He+fow&^^&v$LIZdZ+}Ku7ZB>
zz5T!6j}JbAWf(ZKb}yx54<nIyEz0ug;+6pcf5DfB6*V$#sEY^3uG;s13Xr8nAJ3K?
z$|x;sHIx;R1Q$c#MOPOU@^7=X`1n-v;r2pjn#uN(*X&`))U#=B^uZcM@@VK>X_K~M
zeXG9MN`!DXM1BYsS3|9wW$W;^6ev^44Mm$hx>c1733?#woq9qnM}7-{RUFfoZy<4Q
z5DLbK=p+yR>mV$yrdlyG|4u5o+G=On^mJqura|=Hhzp@{51l+F4DpLm8k8~@M8QH=
zyc&QSbVerB1tmLyu4Uzrc*ru3XmCP^2fa1UOMKs=z{r(K2ctDN&@2aqx)I{sTY%O=
z!59I?io{&d|0j9GHy{MM-8E&WB7Fd`5E!{u<zTcXx24LZ10^6jISAFtI)KDLC?kTe
zu5*0Y*Z42;g@CWE%AwF$+-9dsCyN0H-zbFNJn7{=_m^wzTb0rAd$X~L-c5x^d;5P~
zR9j1KsdG}x0f6}0svVTx;x<)o(Mj{~!DiMY*moCvz^5XVlp{$}wM2>C_I*903$MLx
z*Q&GYr>V3U(d3#D8T+uL8~|wAV18~|w_PWM9zmJijKsVI2_kF<J9z-!pJx&GJ7*3w
z?KYD5lvfVDdpa!43^gw${}Irw&{L``sPc{@F&9Ii=X?htNna(22p12x-@JUHE;rJ)
z@ginZZHfQ!4w=cw0{}?zc?h#(C-Zl?FW^7%9io^jzR{$)#sB~S07*qoM6N<$f_w^7
A_W%F@
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-linux/decrypt-active-18.svg
@@ -0,0 +1,168 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ viewBox="0 0 16.874999 16.875"
+ id="svg3423"
+ version="1.1"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06"
+ sodipodi:docname="decrypt-active-18.svg"
+ inkscape:export-filename="/Users/pbr/enigmail/Logo/Enigmail Icon V3 16x16.png"
+ inkscape:export-xdpi="2.6199999"
+ inkscape:export-ydpi="2.6199999">
+ <defs
+ id="defs3425">
+ <linearGradient
+ id="linearGradient4972"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#d5d211;stop-opacity:1;"
+ offset="0"
+ id="stop4974" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4964"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4966" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2780">
+ <stop
+ id="stop2782"
+ style="stop-color:#ffffff"
+ offset="0" />
+ <stop
+ id="stop2784"
+ style="stop-color:#ffffff;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2834">
+ <stop
+ id="stop2836"
+ style="stop-color:#e59a00"
+ offset="0" />
+ <stop
+ id="stop2838"
+ style="stop-color:#faff7d;stop-opacity:.18367"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3057">
+ <stop
+ id="stop3059"
+ style="stop-color:#282828"
+ offset="0" />
+ <stop
+ id="stop3061"
+ style="stop-color:#282828;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2713">
+ <stop
+ id="stop2715"
+ style="stop-color:#000000"
+ offset="0" />
+ <stop
+ id="stop2717"
+ style="stop-color:#ffffff"
+ offset="1" />
+ </linearGradient>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="20"
+ inkscape:cx="-3.2745776"
+ inkscape:cy="8.1578366"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer2"
+ showgrid="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1163"
+ inkscape:window-height="728"
+ inkscape:window-x="45"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ units="px"
+ inkscape:snap-grids="false">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4919"
+ originx="-299.99048"
+ originy="-124.88544" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata3428">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-299.99051,-910.60174)"
+ style="display:inline">
+ <rect
+ style="fill:#000000;fill-opacity:1;stroke:#080808;stroke-width:3.075562;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;image-rendering:auto"
+ id="rect4872"
+ width="7.236938"
+ height="7.236938"
+ x="302.46579"
+ y="918.70203"
+ rx="0.008277542"
+ ry="0.044642854" />
+ <rect
+ ry="0.0078140106"
+ rx="0.00144885"
+ y="921.68713"
+ x="305.4509"
+ height="1.2667092"
+ width="1.2667092"
+ id="rect4419"
+ style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:2.8125;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:14.39999962;stroke-dasharray:none;stroke-opacity:1;image-rendering:auto" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="Layer 2"
+ style="display:inline"
+ transform="translate(-299.99051,-602.33402)">
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2.00754595;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 308.94445,609.1247 c 0,0 0.0159,-2.38773 0.0784,-3.49702 0.0816,-1.44553 1.96247,-2.34204 3.19909,-2.22236 1.2066,0.11676 2.67091,1.33032 2.77304,2.73882 0.0866,1.19436 0.094,1.94763 0.094,1.94763"
+ id="path1498"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="csasc" />
+ </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-linux/decrypt-inactive-18.svg
@@ -0,0 +1,168 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ viewBox="0 0 16.874999 16.875"
+ id="svg3423"
+ version="1.1"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06"
+ sodipodi:docname="decrypt-inactive-18.svg"
+ inkscape:export-filename="/Users/pbr/enigmail/Logo/Enigmail Icon V3 16x16.png"
+ inkscape:export-xdpi="2.6199999"
+ inkscape:export-ydpi="2.6199999">
+ <defs
+ id="defs3425">
+ <linearGradient
+ id="linearGradient4972"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#d5d211;stop-opacity:1;"
+ offset="0"
+ id="stop4974" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4964"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4966" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2780">
+ <stop
+ id="stop2782"
+ style="stop-color:#ffffff"
+ offset="0" />
+ <stop
+ id="stop2784"
+ style="stop-color:#ffffff;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2834">
+ <stop
+ id="stop2836"
+ style="stop-color:#e59a00"
+ offset="0" />
+ <stop
+ id="stop2838"
+ style="stop-color:#faff7d;stop-opacity:.18367"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3057">
+ <stop
+ id="stop3059"
+ style="stop-color:#282828"
+ offset="0" />
+ <stop
+ id="stop3061"
+ style="stop-color:#282828;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2713">
+ <stop
+ id="stop2715"
+ style="stop-color:#000000"
+ offset="0" />
+ <stop
+ id="stop2717"
+ style="stop-color:#ffffff"
+ offset="1" />
+ </linearGradient>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="20"
+ inkscape:cx="5.7254224"
+ inkscape:cy="8.1578366"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer2"
+ showgrid="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1163"
+ inkscape:window-height="728"
+ inkscape:window-x="45"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ units="px"
+ inkscape:snap-grids="false">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4919"
+ originx="-299.99048"
+ originy="-124.88544" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata3428">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-299.99051,-910.60174)"
+ style="display:inline">
+ <rect
+ style="fill:#4d4d4d;fill-opacity:1;stroke:#4d4d4d;stroke-width:3.075562;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;image-rendering:auto"
+ id="rect4872"
+ width="7.236938"
+ height="7.236938"
+ x="302.46579"
+ y="918.70203"
+ rx="0.008277542"
+ ry="0.044642854" />
+ <rect
+ ry="0.0078140106"
+ rx="0.00144885"
+ y="921.68713"
+ x="305.4509"
+ height="1.2667092"
+ width="1.2667092"
+ id="rect4419"
+ style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:2.8125;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:14.39999962;stroke-dasharray:none;stroke-opacity:1;image-rendering:auto" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="Layer 2"
+ style="display:inline"
+ transform="translate(-299.99051,-602.33402)">
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#4d4d4d;stroke-width:2.00754595;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 308.94445,609.1247 c 0,0 0.0159,-2.38773 0.0784,-3.49702 0.0816,-1.44553 1.96247,-2.34204 3.19909,-2.22236 1.2066,0.11676 2.67091,1.33032 2.77304,2.73882 0.0866,1.19436 0.094,1.94763 0.094,1.94763"
+ id="path1498"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="csasc" />
+ </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-linux/encrypt-active-18.svg
@@ -0,0 +1,297 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ viewBox="0 0 16.874999 16.875"
+ id="svg3423"
+ version="1.1"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06"
+ sodipodi:docname="encrypt-active.svg"
+ inkscape:export-filename="/Users/pbr/enigmail/Logo/Enigmail Icon V3 16x16.png"
+ inkscape:export-xdpi="2.6199999"
+ inkscape:export-ydpi="2.6199999">
+ <defs
+ id="defs3425">
+ <linearGradient
+ id="linearGradient4972"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#d5d211;stop-opacity:1;"
+ offset="0"
+ id="stop4974" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4964"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4966" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4983"
+ y2="11.711"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="35.995998"
+ gradientTransform="matrix(7.1505593,0,0,8.534094,228.71232,184.93067)"
+ y1="1.6246001"
+ x1="35.995998"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient2780">
+ <stop
+ id="stop2782"
+ style="stop-color:#ffffff"
+ offset="0" />
+ <stop
+ id="stop2784"
+ style="stop-color:#ffffff;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4980"
+ y2="6.0918002"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="39.723999"
+ gradientTransform="matrix(7.1746573,0,0,8.6675651,228.71232,177.84541)"
+ y1="11.17"
+ x1="38.362999"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient2834">
+ <stop
+ id="stop2836"
+ style="stop-color:#e59a00"
+ offset="0" />
+ <stop
+ id="stop2838"
+ style="stop-color:#faff7d;stop-opacity:.18367"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4971"
+ y2="7.5862999"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="38.782001"
+ gradientTransform="matrix(7.1505593,0,0,8.6966416,228.71232,177.84541)"
+ y1="9.5746002"
+ x1="38.782001"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4968"
+ y2="-13.17"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="33.183998"
+ gradientTransform="matrix(9.6839253,0,0,6.8812942,222.29523,170.28389)"
+ y1="24.385"
+ x1="33.183998"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4965"
+ y2="-13.17"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="33.183998"
+ gradientTransform="matrix(9.4687954,0,0,5.4755733,228.71232,182.51659)"
+ y1="-4.6247249"
+ x1="33.003456"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3057">
+ <stop
+ id="stop3059"
+ style="stop-color:#282828"
+ offset="0" />
+ <stop
+ id="stop3061"
+ style="stop-color:#282828;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3151"
+ y2="52.609001"
+ spreadMethod="reflect"
+ gradientUnits="userSpaceOnUse"
+ x2="44.723999"
+ gradientTransform="matrix(9.9301464,0,0,6.770848,212.4891,154.91826)"
+ y1="52.609001"
+ x1="29.614"
+ inkscape:collect="always">
+ <stop
+ id="stop2703"
+ style="stop-color:#db9300"
+ offset="0" />
+ <stop
+ id="stop2770"
+ style="stop-color:#fac700"
+ offset=".75" />
+ <stop
+ id="stop2705"
+ style="stop-color:#fff363"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2713">
+ <stop
+ id="stop2715"
+ style="stop-color:#000000"
+ offset="0" />
+ <stop
+ id="stop2717"
+ style="stop-color:#ffffff"
+ offset="1" />
+ </linearGradient>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="25"
+ inkscape:cx="6.7707505"
+ inkscape:cy="8.9438348"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1239"
+ inkscape:window-height="755"
+ inkscape:window-x="45"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ units="px">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4919"
+ originx="-284.74604"
+ originy="-71.484602" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata3428">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="Layer 2"
+ style="display:inline"
+ transform="translate(-284.74607,-655.73485)"
+ sodipodi:insensitive="true">
+ <g
+ transform="matrix(0.03532479,0,0,0.03960741,274.23987,648.41157)"
+ inkscape:label="Layer 2"
+ id="layer2-4">
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,184.8967 c -87.94487,0 -158.74505,56.60787 -158.74505,126.92368 v 141.02397 h 44.09513 V 311.82038 c 0,-50.7856 51.13613,-91.66768 114.64992,-91.66768 63.51378,0 114.64993,40.88208 114.64993,91.66768 V 452.84435 H 669.6692 V 311.82038 c 0,-70.31581 -70.80018,-126.92368 -158.74505,-126.92368 z"
+ style="fill:#282828;fill-rule:evenodd;stroke-width:3.01772833"
+ id="rect2723" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,194.10762 c -83.05952,0 -149.92515,57.12633 -149.92515,122.29246 v 131.4673 c 0,49.3598 38.45179,91.27181 93.15445,108.75267 C 414.96119,542.36212 387.4587,511.79338 387.4587,475.54598 V 309.48131 c 0,-49.83274 51.13176,-93.25814 114.64554,-93.25814 h 17.6398 c 63.51379,0 114.64994,43.4254 114.64994,93.25814 v 166.06467 c 0,36.2474 -27.5025,66.81614 -66.69476,81.07407 54.69829,-17.48086 93.15007,-59.39287 93.15007,-108.75267 v -131.4673 c 0,-65.16613 -66.86561,-122.29246 -149.92514,-122.29246 z"
+ style="opacity:0.65;fill:url(#linearGradient4983);fill-rule:evenodd;stroke-width:3.01772833"
+ id="path3082" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,187.88807 c -85.48249,0 -154.30223,54.6531 -154.30223,122.5412 v 136.95677 c 0,51.41616 39.57782,95.07978 95.87093,113.29631 -40.33581,-14.85349 -68.6401,-46.70438 -68.6401,-84.46164 v -173.001 c 0,-51.91359 52.62584,-101.87944 117.99736,-101.87944 l 18.15245,-3.4e-4 c 65.36715,0 117.99298,49.96583 117.99298,101.87944 v 173.00099 c 0,37.75727 -28.3043,69.60817 -68.64012,84.46163 56.29315,-18.21653 95.87096,-61.88014 95.87096,-113.2963 V 310.4289 c 0,-67.8881 -68.81976,-122.54118 -154.30223,-122.54118 z"
+ style="opacity:0.5;fill:url(#linearGradient4980);fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2931" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,199.88821 c -83.05952,0 -149.92515,45.52381 -149.92515,111.93356 v 133.97558 c 0,50.29514 38.45179,92.99187 93.15445,110.82307 C 414.96119,542.08221 387.4587,510.93897 387.4587,474.00145 V 304.76987 c 0,-50.78207 51.13176,-91.72024 114.64554,-91.72024 h 17.6398 c 63.51379,0 114.64994,40.93817 114.64994,91.72024 v 169.23158 c 0,36.93752 -27.5025,68.08076 -66.69476,82.61897 54.69829,-17.8312 93.15007,-60.52793 93.15007,-110.82307 V 311.82177 c 0,-66.40975 -66.86561,-111.93356 -149.92514,-111.93356 z"
+ style="opacity:0.5;fill:url(#linearGradient4971);fill-rule:evenodd;stroke-width:3.01772833"
+ id="rect2875" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,192.03933 c -117.25692,0 -144.31249,81.67662 -144.31249,118.61765 0,110.81954 9.01707,110.81954 9.01707,0 0,-81.27025 72.13215,-113.34537 135.29542,-113.34537 63.16326,7e-4 135.2954,35.39264 135.2954,113.34537 0,110.81954 9.02146,110.81954 9.02146,0 0,-36.94103 -27.05996,-118.61765 -144.31686,-118.61765 z"
+ style="opacity:0.8;fill:url(#linearGradient4968);fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2933" />
+ <rect
+ x="323.9566"
+ y="357.59714"
+ width="371.55212"
+ height="253.34859"
+ ry="1.9118049"
+ rx="2.3074045"
+ style="fill:url(#linearGradient3151);fill-rule:evenodd;stroke-width:3.01772833"
+ id="rect1941" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 331.08188,610.95331 c 8.3528,-8.10605 83.73569,-14.49145 178.38542,-14.49145 93.35299,0 167.50834,6.30892 177.61479,14.26203 -0.18461,0 -0.13845,0.22942 -0.27689,0.22942 z"
+ style="opacity:0.5;fill:#000000;fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2768" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,197.15513 c -114.64992,0 -141.10525,67.66709 -141.10525,97.06436 0,88.18571 8.81991,88.18571 8.81991,0 0,-64.66912 70.52414,-92.86779 132.28534,-92.86779 61.76122,6.1e-4 132.28972,30.83556 132.28972,92.86779 0,88.18571 8.81553,88.18571 8.81553,0 0,-29.39727 -26.45532,-97.06436 -141.10525,-97.06436 z"
+ style="opacity:1;fill:url(#linearGradient4965);fill-opacity:1;fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2885" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 345.56746,357.59712 c 7.93046,8.11368 79.50178,14.47618 169.36574,14.47618 88.63275,0 159.02983,-6.29748 168.62525,-14.2506 -0.13145,-0.0122 -0.13145,-0.22558 -0.26289,-0.22558 z"
+ style="opacity:0.5;fill:#ffffff;fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2727" />
+ </g>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer1"
+ inkscape:label="Layer 3"
+ sodipodi:insensitive="true">
+ <g
+ id="g5151"
+ transform="translate(-1.0275364)">
+ <path
+ inkscape:connector-curvature="0"
+ id="path5137"
+ d="M 9.8625001,9.3749997 7.1249998,9.4124997 7.1624998,14.1 9.8250001,14.0625"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:0.93749994;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5139"
+ d="M 9.9750001,11.6438 7.4249998,11.6813"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#0000ff;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:0.93749994;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ </g>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer3"
+ inkscape:label="Layer 4"
+ sodipodi:insensitive="true">
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#008000;stroke-width:1.74388254;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 11.90849,12.21419 c 0,0 1.39836,1.035106 2.568841,2.4924 0.454732,-3.871985 1.725652,-6.1303502 1.725652,-6.1303502"
+ id="path4586"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccc" />
+ </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-linux/encrypt-disabled-18.svg
@@ -0,0 +1,322 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ viewBox="0 0 16.874999 16.875"
+ id="svg3423"
+ version="1.1"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06"
+ sodipodi:docname="encrypt-disabled.svg"
+ inkscape:export-filename="/Users/pbr/enigmail/Logo/Enigmail Icon V3 16x16.png"
+ inkscape:export-xdpi="2.6199999"
+ inkscape:export-ydpi="2.6199999">
+ <defs
+ id="defs3425">
+ <linearGradient
+ id="linearGradient4972"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#d5d211;stop-opacity:1;"
+ offset="0"
+ id="stop4974" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4964"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4966" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4983"
+ y2="11.711"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="35.995998"
+ gradientTransform="matrix(7.1505593,0,0,8.534094,228.71232,184.93067)"
+ y1="1.6246001"
+ x1="35.995998"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient2780">
+ <stop
+ id="stop2782"
+ style="stop-color:#ffffff"
+ offset="0" />
+ <stop
+ id="stop2784"
+ style="stop-color:#ffffff;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4980"
+ y2="6.0918002"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="39.723999"
+ gradientTransform="matrix(7.1746573,0,0,8.6675651,228.71232,177.84541)"
+ y1="11.17"
+ x1="38.362999"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient2834">
+ <stop
+ id="stop2836"
+ style="stop-color:#e59a00"
+ offset="0" />
+ <stop
+ id="stop2838"
+ style="stop-color:#faff7d;stop-opacity:.18367"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4971"
+ y2="7.5862999"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="38.782001"
+ gradientTransform="matrix(7.1505593,0,0,8.6966416,228.71232,177.84541)"
+ y1="9.5746002"
+ x1="38.782001"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4968"
+ y2="-13.17"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="33.183998"
+ gradientTransform="matrix(9.6839253,0,0,6.8812942,222.29523,170.28389)"
+ y1="24.385"
+ x1="33.183998"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4965"
+ y2="-13.17"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="33.183998"
+ gradientTransform="matrix(9.4687954,0,0,5.4755733,228.71232,182.51659)"
+ y1="-4.6247249"
+ x1="33.003456"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3057">
+ <stop
+ id="stop3059"
+ style="stop-color:#282828"
+ offset="0" />
+ <stop
+ id="stop3061"
+ style="stop-color:#282828;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3151"
+ y2="52.609001"
+ spreadMethod="reflect"
+ gradientUnits="userSpaceOnUse"
+ x2="44.723999"
+ gradientTransform="matrix(9.9301464,0,0,6.770848,212.4891,154.91826)"
+ y1="52.609001"
+ x1="29.614"
+ inkscape:collect="always">
+ <stop
+ id="stop2703"
+ style="stop-color:#db9300"
+ offset="0" />
+ <stop
+ id="stop2770"
+ style="stop-color:#fac700"
+ offset=".75" />
+ <stop
+ id="stop2705"
+ style="stop-color:#fff363"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2713">
+ <stop
+ id="stop2715"
+ style="stop-color:#000000"
+ offset="0" />
+ <stop
+ id="stop2717"
+ style="stop-color:#ffffff"
+ offset="1" />
+ </linearGradient>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Greyscale"
+ id="filter5183">
+ <feColorMatrix
+ values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"
+ id="feColorMatrix5181"
+ result="fbSourceGraphic" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix5185" />
+ <feColorMatrix
+ id="feColorMatrix5187"
+ values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"
+ in="fbSourceGraphic"
+ result="fbSourceGraphic" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix5299" />
+ <feColorMatrix
+ id="feColorMatrix5301"
+ values="0.21 0.72 0.072 -0.3 0 0.21 0.72 0.072 -0.3 0 0.21 0.72 0.072 -0.3 0 0 0 0 1 0 "
+ in="fbSourceGraphic"
+ result="fbSourceGraphic" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix5380" />
+ <feColorMatrix
+ id="feColorMatrix5382"
+ values="0.21 0.72 0.072 0.4 0 0.21 0.72 0.072 0.4 0 0.21 0.72 0.072 0.4 0 0 0 0 1 0 "
+ in="fbSourceGraphic" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="25"
+ inkscape:cx="5.0307505"
+ inkscape:cy="7.0342183"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer2"
+ showgrid="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1239"
+ inkscape:window-height="755"
+ inkscape:window-x="45"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ units="px">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4919"
+ originx="-284.74604"
+ originy="-71.484602" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata3428">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="Layer 2"
+ style="display:inline"
+ transform="translate(-284.74607,-655.73485)">
+ <g
+ transform="matrix(0.03532479,0,0,0.03960741,274.23987,648.41157)"
+ inkscape:label="Layer 2"
+ id="layer2-4"
+ style="filter:url(#filter5183)">
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,184.8967 c -87.94487,0 -158.74505,56.60787 -158.74505,126.92368 v 141.02397 h 44.09513 V 311.82038 c 0,-50.7856 51.13613,-91.66768 114.64992,-91.66768 63.51378,0 114.64993,40.88208 114.64993,91.66768 V 452.84435 H 669.6692 V 311.82038 c 0,-70.31581 -70.80018,-126.92368 -158.74505,-126.92368 z"
+ style="fill:#282828;fill-rule:evenodd;stroke-width:3.01772833"
+ id="rect2723" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,194.10762 c -83.05952,0 -149.92515,57.12633 -149.92515,122.29246 v 131.4673 c 0,49.3598 38.45179,91.27181 93.15445,108.75267 C 414.96119,542.36212 387.4587,511.79338 387.4587,475.54598 V 309.48131 c 0,-49.83274 51.13176,-93.25814 114.64554,-93.25814 h 17.6398 c 63.51379,0 114.64994,43.4254 114.64994,93.25814 v 166.06467 c 0,36.2474 -27.5025,66.81614 -66.69476,81.07407 54.69829,-17.48086 93.15007,-59.39287 93.15007,-108.75267 v -131.4673 c 0,-65.16613 -66.86561,-122.29246 -149.92514,-122.29246 z"
+ style="opacity:0.65;fill:url(#linearGradient4983);fill-rule:evenodd;stroke-width:3.01772833"
+ id="path3082" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,187.88807 c -85.48249,0 -154.30223,54.6531 -154.30223,122.5412 v 136.95677 c 0,51.41616 39.57782,95.07978 95.87093,113.29631 -40.33581,-14.85349 -68.6401,-46.70438 -68.6401,-84.46164 v -173.001 c 0,-51.91359 52.62584,-101.87944 117.99736,-101.87944 l 18.15245,-3.4e-4 c 65.36715,0 117.99298,49.96583 117.99298,101.87944 v 173.00099 c 0,37.75727 -28.3043,69.60817 -68.64012,84.46163 56.29315,-18.21653 95.87096,-61.88014 95.87096,-113.2963 V 310.4289 c 0,-67.8881 -68.81976,-122.54118 -154.30223,-122.54118 z"
+ style="opacity:0.5;fill:url(#linearGradient4980);fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2931" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,199.88821 c -83.05952,0 -149.92515,45.52381 -149.92515,111.93356 v 133.97558 c 0,50.29514 38.45179,92.99187 93.15445,110.82307 C 414.96119,542.08221 387.4587,510.93897 387.4587,474.00145 V 304.76987 c 0,-50.78207 51.13176,-91.72024 114.64554,-91.72024 h 17.6398 c 63.51379,0 114.64994,40.93817 114.64994,91.72024 v 169.23158 c 0,36.93752 -27.5025,68.08076 -66.69476,82.61897 54.69829,-17.8312 93.15007,-60.52793 93.15007,-110.82307 V 311.82177 c 0,-66.40975 -66.86561,-111.93356 -149.92514,-111.93356 z"
+ style="opacity:0.5;fill:url(#linearGradient4971);fill-rule:evenodd;stroke-width:3.01772833"
+ id="rect2875" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,192.03933 c -117.25692,0 -144.31249,81.67662 -144.31249,118.61765 0,110.81954 9.01707,110.81954 9.01707,0 0,-81.27025 72.13215,-113.34537 135.29542,-113.34537 63.16326,7e-4 135.2954,35.39264 135.2954,113.34537 0,110.81954 9.02146,110.81954 9.02146,0 0,-36.94103 -27.05996,-118.61765 -144.31686,-118.61765 z"
+ style="opacity:0.8;fill:url(#linearGradient4968);fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2933" />
+ <rect
+ x="323.9566"
+ y="357.59714"
+ width="371.55212"
+ height="253.34859"
+ ry="1.9118049"
+ rx="2.3074045"
+ style="fill:url(#linearGradient3151);fill-rule:evenodd;stroke-width:3.01772833"
+ id="rect1941" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 331.08188,610.95331 c 8.3528,-8.10605 83.73569,-14.49145 178.38542,-14.49145 93.35299,0 167.50834,6.30892 177.61479,14.26203 -0.18461,0 -0.13845,0.22942 -0.27689,0.22942 z"
+ style="opacity:0.5;fill:#000000;fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2768" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,197.15513 c -114.64992,0 -141.10525,67.66709 -141.10525,97.06436 0,88.18571 8.81991,88.18571 8.81991,0 0,-64.66912 70.52414,-92.86779 132.28534,-92.86779 61.76122,6.1e-4 132.28972,30.83556 132.28972,92.86779 0,88.18571 8.81553,88.18571 8.81553,0 0,-29.39727 -26.45532,-97.06436 -141.10525,-97.06436 z"
+ style="opacity:1;fill:url(#linearGradient4965);fill-opacity:1;fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2885" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 345.56746,357.59712 c 7.93046,8.11368 79.50178,14.47618 169.36574,14.47618 88.63275,0 159.02983,-6.29748 168.62525,-14.2506 -0.13145,-0.0122 -0.13145,-0.22558 -0.26289,-0.22558 z"
+ style="opacity:0.5;fill:#ffffff;fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2727" />
+ </g>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer1"
+ inkscape:label="Layer 3"
+ sodipodi:insensitive="true">
+ <g
+ id="g5307">
+ <path
+ inkscape:connector-curvature="0"
+ id="path5137"
+ d="M 8.8349637,9.3749997 6.0974634,9.4124997 6.1349634,14.1 8.7974637,14.0625"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#00000c;stroke-width:0.93749994;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5139"
+ d="M 8.9474637,11.6438 6.3974634,11.6813"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#0000ff;fill-opacity:1;fill-rule:evenodd;stroke:#00000c;stroke-width:0.93749994;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ </g>
+ </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-linux/encrypt-inactive-18.svg
@@ -0,0 +1,330 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ viewBox="0 0 16.874999 16.875"
+ id="svg3423"
+ version="1.1"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06"
+ sodipodi:docname="encrypt-inactive.svg"
+ inkscape:export-filename="/Users/pbr/enigmail/Logo/Enigmail Icon V3 16x16.png"
+ inkscape:export-xdpi="2.6199999"
+ inkscape:export-ydpi="2.6199999">
+ <defs
+ id="defs3425">
+ <linearGradient
+ id="linearGradient4972"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#d5d211;stop-opacity:1;"
+ offset="0"
+ id="stop4974" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4964"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4966" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4983"
+ y2="11.711"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="35.995998"
+ gradientTransform="matrix(7.1505593,0,0,8.534094,228.71232,184.93067)"
+ y1="1.6246001"
+ x1="35.995998"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient2780">
+ <stop
+ id="stop2782"
+ style="stop-color:#ffffff"
+ offset="0" />
+ <stop
+ id="stop2784"
+ style="stop-color:#ffffff;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4980"
+ y2="6.0918002"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="39.723999"
+ gradientTransform="matrix(7.1746573,0,0,8.6675651,228.71232,177.84541)"
+ y1="11.17"
+ x1="38.362999"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient2834">
+ <stop
+ id="stop2836"
+ style="stop-color:#e59a00"
+ offset="0" />
+ <stop
+ id="stop2838"
+ style="stop-color:#faff7d;stop-opacity:.18367"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4971"
+ y2="7.5862999"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="38.782001"
+ gradientTransform="matrix(7.1505593,0,0,8.6966416,228.71232,177.84541)"
+ y1="9.5746002"
+ x1="38.782001"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4968"
+ y2="-13.17"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="33.183998"
+ gradientTransform="matrix(9.6839253,0,0,6.8812942,222.29523,170.28389)"
+ y1="24.385"
+ x1="33.183998"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4965"
+ y2="-13.17"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="33.183998"
+ gradientTransform="matrix(9.4687954,0,0,5.4755733,228.71232,182.51659)"
+ y1="-4.6247249"
+ x1="33.003456"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3057">
+ <stop
+ id="stop3059"
+ style="stop-color:#282828"
+ offset="0" />
+ <stop
+ id="stop3061"
+ style="stop-color:#282828;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3151"
+ y2="52.609001"
+ spreadMethod="reflect"
+ gradientUnits="userSpaceOnUse"
+ x2="44.723999"
+ gradientTransform="matrix(9.9301464,0,0,6.770848,212.4891,154.91826)"
+ y1="52.609001"
+ x1="29.614"
+ inkscape:collect="always">
+ <stop
+ id="stop2703"
+ style="stop-color:#db9300"
+ offset="0" />
+ <stop
+ id="stop2770"
+ style="stop-color:#fac700"
+ offset=".75" />
+ <stop
+ id="stop2705"
+ style="stop-color:#fff363"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2713">
+ <stop
+ id="stop2715"
+ style="stop-color:#000000"
+ offset="0" />
+ <stop
+ id="stop2717"
+ style="stop-color:#ffffff"
+ offset="1" />
+ </linearGradient>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Greyscale"
+ id="filter5183">
+ <feColorMatrix
+ values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"
+ id="feColorMatrix5181"
+ result="fbSourceGraphic" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix5185" />
+ <feColorMatrix
+ id="feColorMatrix5187"
+ values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"
+ in="fbSourceGraphic"
+ result="fbSourceGraphic" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix5299" />
+ <feColorMatrix
+ id="feColorMatrix5301"
+ values="0.21 0.72 0.072 -0.3 0 0.21 0.72 0.072 -0.3 0 0.21 0.72 0.072 -0.3 0 0 0 0 1 0 "
+ in="fbSourceGraphic" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="25"
+ inkscape:cx="5.0307505"
+ inkscape:cy="6.0232448"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer3"
+ showgrid="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1239"
+ inkscape:window-height="755"
+ inkscape:window-x="45"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ units="px">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4919"
+ originx="-284.74604"
+ originy="-71.484602" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata3428">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="Layer 2"
+ style="display:inline"
+ transform="translate(-284.74607,-655.73485)"
+ sodipodi:insensitive="true">
+ <g
+ transform="matrix(0.03532479,0,0,0.03960741,274.23987,648.41157)"
+ inkscape:label="Layer 2"
+ id="layer2-4"
+ style="filter:url(#filter5183)">
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,184.8967 c -87.94487,0 -158.74505,56.60787 -158.74505,126.92368 v 141.02397 h 44.09513 V 311.82038 c 0,-50.7856 51.13613,-91.66768 114.64992,-91.66768 63.51378,0 114.64993,40.88208 114.64993,91.66768 V 452.84435 H 669.6692 V 311.82038 c 0,-70.31581 -70.80018,-126.92368 -158.74505,-126.92368 z"
+ style="fill:#282828;fill-rule:evenodd;stroke-width:3.01772833"
+ id="rect2723" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,194.10762 c -83.05952,0 -149.92515,57.12633 -149.92515,122.29246 v 131.4673 c 0,49.3598 38.45179,91.27181 93.15445,108.75267 C 414.96119,542.36212 387.4587,511.79338 387.4587,475.54598 V 309.48131 c 0,-49.83274 51.13176,-93.25814 114.64554,-93.25814 h 17.6398 c 63.51379,0 114.64994,43.4254 114.64994,93.25814 v 166.06467 c 0,36.2474 -27.5025,66.81614 -66.69476,81.07407 54.69829,-17.48086 93.15007,-59.39287 93.15007,-108.75267 v -131.4673 c 0,-65.16613 -66.86561,-122.29246 -149.92514,-122.29246 z"
+ style="opacity:0.65;fill:url(#linearGradient4983);fill-rule:evenodd;stroke-width:3.01772833"
+ id="path3082" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,187.88807 c -85.48249,0 -154.30223,54.6531 -154.30223,122.5412 v 136.95677 c 0,51.41616 39.57782,95.07978 95.87093,113.29631 -40.33581,-14.85349 -68.6401,-46.70438 -68.6401,-84.46164 v -173.001 c 0,-51.91359 52.62584,-101.87944 117.99736,-101.87944 l 18.15245,-3.4e-4 c 65.36715,0 117.99298,49.96583 117.99298,101.87944 v 173.00099 c 0,37.75727 -28.3043,69.60817 -68.64012,84.46163 56.29315,-18.21653 95.87096,-61.88014 95.87096,-113.2963 V 310.4289 c 0,-67.8881 -68.81976,-122.54118 -154.30223,-122.54118 z"
+ style="opacity:0.5;fill:url(#linearGradient4980);fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2931" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,199.88821 c -83.05952,0 -149.92515,45.52381 -149.92515,111.93356 v 133.97558 c 0,50.29514 38.45179,92.99187 93.15445,110.82307 C 414.96119,542.08221 387.4587,510.93897 387.4587,474.00145 V 304.76987 c 0,-50.78207 51.13176,-91.72024 114.64554,-91.72024 h 17.6398 c 63.51379,0 114.64994,40.93817 114.64994,91.72024 v 169.23158 c 0,36.93752 -27.5025,68.08076 -66.69476,82.61897 54.69829,-17.8312 93.15007,-60.52793 93.15007,-110.82307 V 311.82177 c 0,-66.40975 -66.86561,-111.93356 -149.92514,-111.93356 z"
+ style="opacity:0.5;fill:url(#linearGradient4971);fill-rule:evenodd;stroke-width:3.01772833"
+ id="rect2875" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,192.03933 c -117.25692,0 -144.31249,81.67662 -144.31249,118.61765 0,110.81954 9.01707,110.81954 9.01707,0 0,-81.27025 72.13215,-113.34537 135.29542,-113.34537 63.16326,7e-4 135.2954,35.39264 135.2954,113.34537 0,110.81954 9.02146,110.81954 9.02146,0 0,-36.94103 -27.05996,-118.61765 -144.31686,-118.61765 z"
+ style="opacity:0.8;fill:url(#linearGradient4968);fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2933" />
+ <rect
+ x="323.9566"
+ y="357.59714"
+ width="371.55212"
+ height="253.34859"
+ ry="1.9118049"
+ rx="2.3074045"
+ style="fill:url(#linearGradient3151);fill-rule:evenodd;stroke-width:3.01772833"
+ id="rect1941" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 331.08188,610.95331 c 8.3528,-8.10605 83.73569,-14.49145 178.38542,-14.49145 93.35299,0 167.50834,6.30892 177.61479,14.26203 -0.18461,0 -0.13845,0.22942 -0.27689,0.22942 z"
+ style="opacity:0.5;fill:#000000;fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2768" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,197.15513 c -114.64992,0 -141.10525,67.66709 -141.10525,97.06436 0,88.18571 8.81991,88.18571 8.81991,0 0,-64.66912 70.52414,-92.86779 132.28534,-92.86779 61.76122,6.1e-4 132.28972,30.83556 132.28972,92.86779 0,88.18571 8.81553,88.18571 8.81553,0 0,-29.39727 -26.45532,-97.06436 -141.10525,-97.06436 z"
+ style="opacity:1;fill:url(#linearGradient4965);fill-opacity:1;fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2885" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 345.56746,357.59712 c 7.93046,8.11368 79.50178,14.47618 169.36574,14.47618 88.63275,0 159.02983,-6.29748 168.62525,-14.2506 -0.13145,-0.0122 -0.13145,-0.22558 -0.26289,-0.22558 z"
+ style="opacity:0.5;fill:#ffffff;fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2727" />
+ </g>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer1"
+ inkscape:label="Layer 3"
+ sodipodi:insensitive="true">
+ <g
+ id="g5307">
+ <path
+ inkscape:connector-curvature="0"
+ id="path5137"
+ d="M 8.8349637,9.3749997 6.0974634,9.4124997 6.1349634,14.1 8.7974637,14.0625"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#00000c;stroke-width:0.93749994;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5139"
+ d="M 8.9474637,11.6438 6.3974634,11.6813"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#0000ff;fill-opacity:1;fill-rule:evenodd;stroke:#00000c;stroke-width:0.93749994;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ </g>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer3"
+ inkscape:label="Layer 4">
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:2.24979424;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 15.667684,15.742702 c -3.40903,-2.920537 -4.694046,-5.522759 -4.694046,-5.522759"
+ id="path4586"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:2.15561461;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 15.747607,10.026563 -5.09709,5.759107"
+ id="path5174"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ </g>
+</svg>
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ae175f67fd5e757913691a4773272cc7e0f03d10
GIT binary patch
literal 1017
zc$@+G0|xwwP)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D1CB{VK~y+Th16S2
z6jvAr@DIfcTdip=n#LHmF@=bs8XIq`4KZF5q(KD}^np;Hj2fdzQn41a*-&hV7bHkZ
zu~s2?DNptCU}EY63TnA1i-7D5aIA~E?k>yh?7%Kx|FfK8Hnj0e{+V;moc-;YZ!=5p
ztB%4XtK(SW$@YTJi=PxM*d&*UiDHo{NQ|6M8YP;m6Kx(L+8WvPe`TVb_7wHy?cKuD
zcvy(q40aSH7fK`^NkHPvIWi05jX+Ni6Xi)_Vs#WRwssb$sNP54vG-}d@U>=(fb)BH
z{qYSyJG$@(AS;nfWIj*}u8YG&cA&ol-y)gl>L^K?89|%vJoNksdmnBP-j=V0yI~t^
zY6%b2<*<AC$2R~~0$B%iL5OG%atH9Q2GJLiimqoTmt^(TZ0F$1YW6+e%-+@v;cMF|
zl&UrCx`p*SMeN_Oko6g{{eX@D&c^jRzN7{Cx0uh7RCINhF3IYz-NA;dmVIqm>}|^y
zKF3}$=s3jgySc1a=5lap5^I*&QJn4;&>Ex*@Vy{WI$ASbQqlFiY-vjO*=4N1sOLa$
zDSEt540at80p&;5o9t}lZDr%rPuM>>h5@|*uHgC#a5ct@%pX%y@2aC>g|)lvOXW>N
z9_ylvM?Cjf?>{g6$|<2Ml^nbYcuOj4AH=anH0mcB9wG_=bfOIh(S~?Qy(=Yxu2kF)
z3V@yAKwl~Ay}zK>jlvkXDU6;gJaVp(1Bq7lPn~3<2JkT8AWr-zPP}nK8zSh>s}%R6
z6C41lDJNKSR<K&TiF;~x2;)_=2tRFLBX>J%A5IM!lVcS0)d|?ZOYs))c_cBa4ax-*
zU9EWjDnU<A@laPj2VIxBw=RvH)wV)s)v5!Ia$C<pfsK7hvvi<cKri6Yuc8Gmj60FN
zUZRgAX`-pt?u)AxZ|iA1_D}3@-_60lf8&wCCRQ%4!2!nV?fN+h+B{1MfcwZLJa(pu
z;o68VxPjiTMW)H^MCv7=&E<G>JNsJFfmU-!YmY>|{M9&&4<!RB2X+n6F3b}GL^iBd
z;zcRK{IjSwsCB6v>U+$d2g}jh3J&Qin`{3@ZX*C`0IGl<GJ9#B4*i{nc@D;D$V|C)
zta`7l<*-NP@bK&Lm}~O(c;Te`fgcB8!%YhkgK->^jF_j5m0QQEuYMi>SLE&S0-6rM
n{9l-DqUM@;MJ&pMH;w2YsxCo>5wLfA00000NkvXXu0mjf3)k7T
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..265eda8ebf08c7d2ade38b731cbae96db604d38f
GIT binary patch
literal 715
zc$@*t0yO=JP)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0$@o*K~y+TrI77Q
zQ(+i~_o;9G4}&OaWJzJ=TOv^kQHU|YJ|;m>Ayj@yGDBLj#ju(&DN}qKk|05E(uaZw
zn`PyeJ9BRBIh&7d)($&Ix1G&tzn(KsK_E`o1vfn0&;7gLzBSo!x8`fPmYpV7^N#kf
z&D*Qg_Wz+sJ%$24G}b_)6IUp;)quO9YgNB<!`;4D>uE3G#mQ?Br<<tcsHX}4b%dXt
z0e5qEZgRIVKQANwVnEDy72?OERGd0Vh4EU%115yW&SB=(o~_GgOojU3&?zKCJ&-<C
zLv++o$$6TR?lOd5>5-i8X7Y=08w(rN$mZ~x3-xohI;6rjNKQROXFZj;^K_BB1mXQz
zB!V4Sj=Y4ovqDfKo5N?>SKz%@1bKcGiGZ2i_yS#=ZJ`O?2zl;4+hhP~=^ePiW2<T;
zJ8tVit=F=jM8+>erWT~5Z<(x%E|Cr@&p$%)#|ToAo!$B=Je@^v)Y!(5jPwu@?9EJe
zm#u7u9AL801WhfxpsC1nq+?FD$!qv~D}&0srs-Fn8egA`^kFd(Cfkr;(l}{D+;0VM
ztHQUTI+_w3bai<F@`4?tzj{rY4brPfOG)&zk=?WdvDqdh!-EJ6ABVrEq}AVD+~|5x
z<_nJ0!qZ#MEAvWjN~gnmrAjNb-o8>II_|KwZ)3^TfaLdnq!&Mc^p&j*G2Bl|@<`v|
zUDJ<s+tk*^Vt#;0r@Pqd21rvUn6w8gaaX4J!ScW2EyE92U`6H;wI5)&wPxN*?@y^?
xTPHA5iPhzw|CG{GzjG%zdIam0`kf;h&2Qq9)QKj11kwNi002ovPDHLkV1f_TPLBWp
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..b96f683eef8cb216a146981bf361993e9ce17008
GIT binary patch
literal 708
zc$@*m0z3VQP)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0$E8!K~y+TrI77Q
zlTjFl_o;9D9|lp9WR1egw}wP0L?Olmza~LYAyj@yGDA|CA**I6WsdnWBte4Sqz?%Z
zHp|Mby*M{_p3P&MwXtWm?b)36>pbfe1g36XaKqs~_wR!HR&0f<wLs-;+1=%6-qrcJ
zd1n=G{~xl{-CiUFCmLvU`U<7y8aQthw({@%aCPoixjTylapoFCdlMC{^)%_bj?mLH
zaNgXTpWLM@P-dl{+7b6&h4}tB6=zS;$W$#7{w{<j&S9=&U+(g`F10o=avG`N0Ae4i
zAzEvwXsf5BzZ{{LTBMfxnfxMLI&~wDY!6SjTDxejLpo$e%%+7b747Hgit`d8Z_gqb
z&|@|H0-hdCgh#fA*KnZ7bGI1M(m0a-Zg%4fRGe?2NkIo`@gCb`2$|R$Ob%<-d1Px`
zy@ypE!$A_Bx(taLkcqx#vOX#<=&7{y5UH<YNXIPf)=%K>DW2r9%^_Lo5fZU9Gua)s
zayKMDlLe<~I{2KX!_Sb3+t?<r;4@YR<atHY4<(OpOji1!ghXcAkYv&XX+y%-3&C84
zg^@a%j#%mX>N2Ed3&>#gFX>hgqbw~W(T_TIQ$6DIO-O}?;U7H#-#}@rufL?xalhOf
z7^{WbSRu&sJo;~lQKRyh%7|#a&DOq!?~Vqfz6>I>@(#pQ4nC%EJuX!e)6qTlM}<3h
zYjcDlCbjpm)!Px9J;|g4SW7svB@9>m5$B9Bs==BhAZj_pZrhuE%e~*l$8yI%R*Ci1
qZ-15C<KOuc7(a%Miht*cLh%!e#=5wz>H=N>0000<MNUMnLSTaCLPo9t
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c8e868c2c5cfca3833de5fad81b4073154fe48c0
GIT binary patch
literal 729
zc$@**0w(>5P)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0&Yn}K~y+TrI6oC
zQ(+j#ch#MLKzD;ENwTD{QfDGj3Q>qL!7h^^s1T}MB$*<a%#c+xCS?wP4M~uoU(!WE
zgw3*Y%WclBy=U_^XKn19x$SID`}Us0F-n%&`oI?s&+|SX&hw}?!qt?kb~f&4cQkBo
z``oatLMi_rlGNRr&&`k4(a88^N-b4zHs>`d?d)*1?NPhi@;QFuD)=cK6>POM;k$<5
zlhbfs-<6%*rO(ZgrJq_6^Iie}{s`qKPtfpKHRAqu1V_(e`o`{mmru7BXaj>Mk(}>=
z@SzgCt%?fvQ<QXT5PYFUa;}@nFTkZQs8>cdho_@JJ8P*yDrkXV*Mhg#Qo(tSMx7TC
zetQOqfB{RP=kRovg_V)b;Wh5f_uMIjI5&cXzk}WQJdMsY(gdf6ID40E(vP(82F`(_
zE6T{mxJ(Ds9^*a|8oLCM8j+5?X0k52NDNe*dw}G(VWb2ryY*wZI}2YaW9vg?=|K{<
zHZa+3wsHqVKa<Um)6~K<nhHHdI%a2^yoAqO9$@b^D&3E*DqRwCG9gRv7m@Hp3ldBk
zCoPEkOyDdP_%c{SQ(+rjSz3U&U<K)`l%#59RP|ReF-y`C68WfSH#HzOqeC({0RPZ2
z_<D+)eBDL$j(Zw!V7MA?b15gimj_pcYa>C-Wol1v3E^$G*xEO-=%_>TYai0lcObo*
zUw`3xRGdS45AB?Kn72hKtuN;KnRKd)t=<Y@@;H<BU^(uP!wr;XzS~-U7p}q$m0?-r
z5V7uOw>8P%Qf)QLv-Qa&_79h1W$F7Lk!ngidjcbekts?$OH`_#On2*j;&HYw00000
LNkvXXu0mjfCCyFH
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..322698b33fb416c5d3a8239f82d4f0f00a720bae
GIT binary patch
literal 675
zc$@*F0$lxxP)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0ys%TK~y+TrIO2P
zT2T~+eS!8FoOaMxD5Q=%DfAUeXPI^64bd2pAl|JNQEH2b#)MFBp!I_HE8ex>$smC^
zaNs~Y>)*3B=g85fG);e4?0xp-JO5s5$^IsdMk7}$l|Hpv{Yj-#y%LuHPXsj>4A&PI
z7szBX@caGHYPA|+`*AWFjq;O|lk03Yi$bA*L?VGorGiK#0-a9xIJn7VdO}bFmP#c=
zqfwA+8k@}~VzC(Xdi}q{dAD!q*~8(GK`t*ZnPZ#HhHyBH!C-)RJPtlh$HJ9|iNJwC
z0MqFdS65d^rBa}j<Z?Oo-EOzRaUAS+`%1X-FrA&9JvW=pn9XKrwOYvM^C*|gXfzt=
z^?DeO$H;ajIJSNMd1Q!vdwF`UIWk6H2-okI#bS~3P%fEFqTB7_=H>?dexJFd6wK#y
zIFb##{g8yhe7+0u2LfhaB9vOI)9Em`1iihz#bU8wQRsiiyN~G|kR*lw=A58btCgcX
z60};aveE1HA{Y!}xm;qi*}(R-wgVl4DfV0wl&?5fEEd_5sm&&n34A^ub%KVE_k5wY
zRM6MPFF&N9Lom5!P`BGH=Pz;8{Pgs6I*rk2guA;tI2;bJL%jIcJasyq&lJX}P6|?-
zO6Gj*922x&ucKD0vEHQKU9DCuTrSrREQ)eSu0)X3@m`KS9uL;*HA0~fYbBb~ni%ZR
zfmnM<;39M{zf_#<cDom}CT#yv<V^el8xOmI3Uprx+mDn?_6v`aJ78M+rAhz*002ov
JPDHLkV1lYwIB5U?
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..6485acab9b32cbc5d333cc9b13c2ec277f734763
GIT binary patch
literal 757
zc$@+C0t)?!P)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0*XmQK~y+TrIP(i
zQ&Ako{RjLX22oOENzschDNDp43Na@5YZ3&NLS>L-lhK-+h}9~krm@r^twupFl2OnP
zrlsX{+f}E#ck|d*Id(O-yZO@7xtF`OEhDoB9?s#se9rUzax(rVMxCW_)o(F4>o)7g
z>Nb_i%l{`4I-$+x{Jm8qG<cp6OBq}Z+tl**#>D7$DkgN<96xjkyrY)z_DT}*Tt?vO
zak#E*-x!?HXJtyzPg*QaT?GH3gzzIrNwB{hVXpy!-jf)=y5ryB<AxlSulpFH{tnC!
z9|CVLBm8J3VNC@Hyi_4NYohQoVDvdP@|E?;-JGMEu~Z-yuwZ^v1rf?SPLT!ISp?sm
zK*ZOArMVYy-z*BsSJtN~<F0J?jXe-%ZHRcAX^&46ezKlKI6Z`!TeOl+3^T9MaQng!
zrS{RAgN-(|QunY>zP`4Y)_n@MaW^~He+~jMVmb7d!rBNw-9m)fc0|8g(f+Cr)g~K?
zn|?X|fQ(5MD?#_O!GSsoyG~1PhTx?z{~(F^pOe_!GYn4*pt}7@0w^jO{#$YhdN-F1
z4rvgf&@ih(*wYHmQi|#B3K9$2$?DP<n0osXpmeb4l+^4!5t`42KI&;tTd+7;i)f$=
z-k!tobmXZ$rra9moq{Q!wH#FjqmT?r2aBm?XpzEg&S!c1HClEPzB{WB{d^zG3-7_2
z3m_e$VjkyZvgX3AC5;a>$)MuKq&amWG1p0<jy77l7V{%VD6|7BVW(uS>p%h=m)Juw
zRUDL@IHkv2PZ3rG4xxd)w70F&TwIG=I+P~qdnLi+(3JjqtxBveeM_;pCU5^yVzT%H
n(wP1me70h&WqEsp%E<T$0KYgZTu2Fp00000NkvXXu0mjficwgB
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..156d9846dd2550d3c98306fbe8ed23d92fa9a725
GIT binary patch
literal 702
zc$@*g0zv(WP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00001b5ch_0Itp)
z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iXP)
z6CVfuPrMue00KTqL_t(I%Z-vjOB+ELhTr*iW|q2cDZ3&ojW{3yF-tZC4}u`}*0Z<%
z0Z$Upb14au+(Z!k3ks4`bJ2?%3tsXI>Y-qho1(;QFiw<APX<dAeXhg%d@u8T3~x{@
z7S&)d_|6!!0UVY}rP}j%>#eX5q8>%jm)Y6bED?Rn=kuu7>!)uD%H^`+oYxl@7iS#D
zfz}$SR0_6juVk}X#BqH3s$gqt%O#@5($dll=Ny-pmoUcQ_VyM^DM%?-qA0@2$;l~y
zNnlyl*B}UHeBa0U`8k9T5JI5YY~u0p5t&Q|L{zL)Ds6g6Ktv!SaLys6gl*ffZ5v@2
z!f_n*`+ekcIk>L-<HZJ?^WRrjS73}`YHAAIZg<$i^E^l?A*ICF*x08L!S?pH0`N1F
z$>8qp4nYv$=H><u4-dmO<KyG#bUJ9a+rQZJ@XpSTGRDMV7^;bh2@D1UNGaj_KD5>V
z0M~VqBngg=j#^slW$SsEh~g-Ul;?TST0;ndMx%jdvx)ind3c_OBuQ|5eB8RfzhB<i
z*tlQ-u)Djf0K~amPC1T)Znulc$w{1@ouSw36^$`H##qsH-498U{58g`6bgk40APE2
zd&(FS=ks|brNs61HBzY*eBVd4TE+VM`qPk)mSug`TL0PH-0TlOoG}(JEG#Hv3|g%g
z(&;pUAQ-Omi9(^!0ni!Q7!j$txjEEoHH2Y^>FH@492|@YUV{@6gb-L=UH$Kn*4EY-
k-T@~f?C<Z7T;hBA2R%v0bjV_-4gdfE07*qoM6N<$f<jb3DF6Tf
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..b5e907a99f19128410cf0328380b3eb8cbd4e3ba
GIT binary patch
literal 819
zc$@(<1I+x1P)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0?0{3K~y+TrIKAt
zl3^6bPu<i_-PO8j^R|mFx@xP7yy+&_mFuc?*Hv4$ZfzorR5U~<_(8-JrZz<!Fc)7b
z2~PY(nI8$X0X0#OSuukZ`10?0USDf&T8sYM*?G@--siV-&U5H*lFQ{)_4W0}hK7a#
znM`(HO8=iYw5h2nX}8<q^?K3Q*9V0{A(xITlS-wkYHn^$PEJn3@At#$bRrlG!eX&N
zsZ>@5Z*6V8z@Z#CJv|Mp)e2rU_lt{*7#kZyV`JmL!&$Wtc-!mi>mtPAaEKLawOR}h
z4`Xd@4K|jyJb$PXTF6{h(lrpBk*=RE4mO!gh(sb7A0LO??FJu-&*u~4P$-0CEP`Nr
zO}_M+K9pOiuKbkFN!L%8Mx(i|R;v+>MzOTCgsG`1%*@PSVPOH`a2W45Hn1DAW4Grj
z_TP|j&`qJToNoQG>W+?%Dh5rsTrRAvtYB+v3#+TEVwHRZu~-b-pW^sDQj5$emEY+I
zSt|)bI~gIT^AhH`j-Y(CmY0{sYB@BON+BMPi<Zr1u^k*j)^w$~;~?C*K}a)yFhpSn
zu9i^0#i5;@oe4fi4xOEy6}`b=fZ1$DCX+!vpGPXXj7ay*Z*UTJm{oHy^E?*1$-qW(
zSwbE{*^4Ivfq>XDU$f24O&E<vz6o+>bvm7{VCwIOUHdrmedO#m0))Ju@Rp6Dz&wwO
zqx7--itdWD?(OZZVwVzp`FZOej|Us<ank8Dy1Kg9KZj!7T%2fobi1fOtKD~zfLX$q
z3Br?F!dK>boO9EG@RBs;XLN};bbfvwb8~aB*=*wO?(OY~*3;AT3(QrPQBARflJFi@
z_`^ro9AK3)&trH9B`qb`IPQ^NuSYJI!{FecxJvw+=Z;KbiV_s0E-5KmYNL2bbAs_a
xI~U~ig81VU3Wa0Fa}uj^@drA|q~i)j^b--WOCfKLMJWIP002ovPDHLkV1obUep3Jd
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1e432f57d1d2a4afc2a7ea1789e12cebe3a58744
GIT binary patch
literal 615
zc$@)e0+{`YP)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0sKisK~y+TrISf(
zno$&nU3J&~2Dc65r*zrh(Op+ghyx-b#wigMr7lDi6+w-lbwJ~+I7MCLlR97!7cO*H
z-rjTjStz85+7}L2?>*<?OrPW}DV0j8OeS+H6pC+hx%`uO{eL2;R;!)GVliYg8F;;3
zs8lMY_<Wi4dc9Pq)6KHkEDD7JlF1}0l?p<k5Y%e*WpIPR@SdOqER{+Khr^((>1;F_
zh(sdLXf)@;S+<|4?7?8bA@O*eXMA&WgJ3X-e!q`sGz!+HFJj1PBCyZr!+1PKB9TBk
zod&fem&@`0tyT+yAi!$1ZpDz(WHOnqjYcCTlL?y5Ci3|_%H=ZZ^*XxUE=HpfmdhnP
z9?wrP<ZYSFW+{UTsZ<J`P6vy{0=-_3XGtxXPN!I{R=gUPz;`k1bseF!TJ3h5XG>7Z
z?&;}?+h()jr^Dg+LH=UszeP?Ki$$P567=EWfuAmy3x2;J&(F`;@AsI`=eWDOI~~d<
zE)<JJUNWuO<KrXTZZ~a$lG#@_r~?g@`Oc2e?$s4uG7fb*ol+Jg(DGC1_xJZ04u@E;
z*Ra`aznz#^H6QGD`$zUQz7wxr2NSebtD#!0^4+A}-EOzsSRem@&(`hjEq1#d0)YTu
zCHhTwM-$!77Y*ldIQ(UFCq7>;8qiB7K3^z_<QFDc)&Ab*384T0002ovPDHLkV1k?-
B72*H@
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..8125f28c3edd235256dfd2c9d164950398d77b0a
GIT binary patch
literal 604
zc$@)T0;BzjP)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0r5#hK~y+TrIWd;
zT2T~+t<ojWV4EV3a?3nNx>Qbx10o{M11d@^L==?^PM~qXdBj-@kHi5*EG)DOf6m&R
z8we!#s`+8Db?>!4)^tQ~NiLU*B@&58CX;=WN~KT2_5X>WYPEV5kH?YCX5sVspin5}
z!t-O&YPDjGMzhN0awryyNG6l0R;vhy!%!-fkHK|1-Ft!(uv{)95{ZDarnA**AsUTB
zrBdAw=d=AxWsgQ921z6mEMv3Tj8G_q;c$ppEC#+!Uxbj`L}0()kJ)U7=jUgn(`isk
z^7%ab-|2L~aU3ib%Si~iO$LMElU}dKd_G6J-A18MK&4Vav)M$y-^XM!!FIca*X#W$
zguE@I(J1DjTq>19uh+w7v%z36U|CWN7K;UTyB({BPvE-{_PSo6v|8P6mt{*(%5J$_
zGCLlR?6lkMKgeGQ{kO=;WHNEoM}pRBHFmn)ZUh1W91aIuE*GrVYq(sl+oAl#xl*ab
zN~SfNPN(pAJhTaN-oEmKy3#;--_=b}hr=P}gE(4#Dm|0QU_2gUzu&`ZwL)+c@zs2=
z*=!&AukoF5^|}b!Xf#l-*V%5;?w(F3W_%xigYTBp>BRYbMlcv;t3<!)?!^SR`$faK
qUax-`-3iZ+iw5*J6P_QGNc0P14ZK<qepIOd0000<MNUMnLSTX@>k(K0
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..9c8de22de1029785189e624643a4778980601516
GIT binary patch
literal 640
zc$@)%0)PF9P)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0u@O_K~y+TrISf(
zT2U0oU3Hg!2De@G8{{i=*?qr3SLw=$;DCsT8a0Xrr7lDi6~PI#4#YVs4vD((NgPnb
zg$t#d_TP6d&(H>IQu~L))qBtRai%AFNpiVdERjf@GMVhFR4V--T>qa4s#2-O;cytK
zR0=mYH&7@Pa^d+jX*3$KTCE<Z(`n>#ImBWy6pKap{eE0tUY-WmYPDwsC19aYKp+qR
zWld+TRzoltgi@*ecQ~KzM=HD9?J`It5@8veOeXkzK6E-AghC<kZTc*PyiNpmyWQyb
z`-n!PNF)-VmSi#+_W$ng4jjk9Y&Nfjkk?7C*T2{4bQlZ<sMqVrX0y1xy+yTJMXS}q
z{rx>=vl(12*AF4&Wf=?xF%RY9@i>~zCZ^LV+U+*Wl3Fkv4l$q4Sv7nD--NK^+Cyoz
z8jS|amY|f~Xf$HBTCLb=wOSvO=1=FMi=Stri|=4|NCY(+jU4rnprulYoeqZs9*+mh
z<r3TN7L&;YcDwzN{Dn}_U%}+WPn^r=^Q>fAvxkQVIGs+~1UYYC_(APxpuF$+3AzV9
zH<Ha}6Z1hFEkBi>OeWFm^{`khaCLR{lm%bSTZ_f=j{h3p2v^6!1g%smD3{A@H)(g*
z>oqgJkB9sAT)zYNy1u@~X0w6U>t(A%zv*sog4=N&0@HAIyWJl~cf#}Oq5&N;;rT>~
aM85zx@$O}Jmf1%D0000<MNUMnLSTaKq#zpr
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..95df0702ae44ca263e133d74e03d71d4a2efc6ca
GIT binary patch
literal 730
zc$@*+0ww*4P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00001b5ch_0Itp)
z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iXP)
z6CN=r?ij=X00LS`L_t(I%Z-yuNK|nY#((F3Z_YT*ORdnEbS{NiQPCUI63K*;9z<Y-
z60`^`f+%QFM7fA4DuSq0x{2Df>#>Q*F@uPp=!ue*I+mr7=p@bLT<^a{p^_7NmWT8E
zIGpo+!W5Ns=UT?7a{_RU8tRri`TZ`_t)x5GO6Dx-vTGW(U4L7TlpfQGtl6g~&Zz&v
ziR}3no=}Uq=Td|`6Z-F?%2#1-JkyZy6^@A*Y2nO(PiFX^ne~kkMq@ZHuYsL{ul-1A
z8HRUF#qr*EoKAj!lzszu-X^tZR|G4F^P&mK3?YHJIG;M9`2kY70<-!;Ln6EQkeQAb
z!vHe~9uIiuAl@)|XJS+>gMlIRk3poi4E22Z$+QBPM!VB~17pT+)Vg|zy#enm@aMt;
zKN9eR--Mi^;3R=W{z?mR-7%|=qQ7(^YnP(^ei;6V_<RubL17N`j-tCCw#r0!q;S3E
zI8M~qamtcN0PGYJm<>w{U|<*=4VDkSY3w8WdgMo}+?WV!t)s^7bC!fXFxZFqLfDOG
z(NEe?HC3=M3wj;w!v}k0=u>${>~WX;flDlHxTC7>q9wi*d~QdUmtkKy4+HNTfCSJW
z`KvQ^->Ys(#VaymPrCr*SMhcWJ03OmUbRsD@U{i1tb@fNQng!fe`pqfM7YQ=S@XgU
ztsgz_e~ygZTkGMdxxdLG-huPvJgVk0lobB;>G+8<zjVZ*BR=V2C}qjc0-OU|QQK}J
zRYjyWSN|1IQ|84Kae-H%uD>2PlDKfS-IM7A&csyM8+QEDRnt!FZyv$LNUg?PtN;K2
M07*qoM6N<$f;(tYTmS$7
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..9da65a84e2165c1247fe61c84d346cd7b74bc282
GIT binary patch
literal 749
zc$@+40uud+P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00001b5ch_0Itp)
z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iXP)
z6CMZWRE=E#00M1EL_t(I%Z*b_NK|1IJ@@;WH{;BEGozr%AoJ8DMWiV!haekVq(}u(
zNl+jXE!wt@nu6$OUAS};1VTiAn;=Ic$O?)U>5#@){-utn<L}J8d0&eXwG=(ud(XLt
zd(S0ogZ^lxsVuq*f<~k|t8T`hudmZ~WG0>6`Qf^t&~+>h4q|@bTeRwW-;LECz9so~
z$Yerx7acnu)CvU{jl*451tyKi#p)~`bWQebDG<NAM`z^tP}F!PD7<M{sm}mxWM-0Z
z?+$@UJ#wi!gZr;1dc>yV$<^cvlm(r;ijf}c1PKoq&qJzj2-$^?@YOT|kkR-(Hp@2S
za}E%35IF&<gK+~SyeK$)2vYaK&dnqLKr<ZP{A-&!U=AK%NhV<DoRHoy?DPnTc_4%b
zQZEMy10;2Dx7YWdK!1DKR5|;ZzxD{AMj?%Zu$FWvYY8NDkTgJA2{N-&$jpwt*>J{0
zd)UkYG5@{>GcQmCr7V#0fjl)Jl?0%`oF%a4k{Ew=XP8z}^?W_qmK5{XHk+>E0w`+^
zuzi?(_ZZ7RKfzso0z$7qHZ_G`UAKm5ajw4fa_<lUK%ygTD#c^|&`DD(aKKKFK^S{6
z(ftVa@=O#8%ZgZ3cnV$C!qg8!Hd5NwI|Klf=!lqLxtKq6+SGhmSPOAT|50#HB}SfH
zL22t-0)T;sb%wOHz@l^ddv(j#%&K!iG8glO&YRLbjm%6hw8B#m-r7HL;A<<YxbQv&
zAocIk1q3ra<xQCX(gH2e2H|hO$kW!pRkq!)K@evk?uN1d<ysIqL-1^jumcF<45Kg3
fZA>y}*eQMkcO%GeB_#9*00000NkvXXu0mjfvp+-x
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..bfb4f372c22f0a0ea580319fc2b02cfc52bb5ed8
GIT binary patch
literal 797
zc$@(p1LFLNP)<h;3K|Lk000e1NJLTq000yK000mO1^@s678qX}00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0<uX&K~y+Tjg)Oj
zlTj4M&xRvin1tjaKWLOCld>?V{30faVv7bPL_*&JsRe0;)WlU}^&u$eRm#MSz?2fS
zzy#Akk{~L4e_LA#x-rvd+cRJ0PXFs3tFa0G;m<wyKF|5>oO}0NTCFCV^&72kQjNbV
zH^noN23Co6jOqZ9&PSBDKxABcc}W)nu}T!C-s;2@TVvLY^sSC<b%h>VQQR>=VgwA1
zL!1lPoq3{CC1qs^*l<(6YrM_KqwlNu^(g~Sn)b?5TQz#Xw1A~A5?u$Z5zs?QT4XK5
zWczKF1}?MhX%0I~g)AT5$M!wya=^CuiHhMi0Bh!mb}Fgi`X46SZ?QaTVX3Kzr?V1R
z(rFgGTB-!_I6w`k>?@#Um?tRdp$dc9uCcejjO7PMSW1pr@TurGs(A<43wQ{?&RHS@
zwjf$T{8L1j)y&?mVs;vI@~oOBB%*l$#sRfrbO2b5I87Xqf(a@j>_<6!21?m(PMPsV
zQ>&Lq@(?Y~01B`S&=}f1hjuq8n4ngKja}r?hdZX+I`t))s0T1NK(lT@#fS<!P+@LJ
zyWE}?^|LvDI1+5QW|wOsQ*#hW_e7gw&)_it=kRBq!rCKvTS&Vj^{E*{E!lo&pCu3i
ztKIGDJ)=znEDx=<0@MpXLyPs8PaqzIPfruAS5QF>)+V2Ib>C%c!vS`DHwTtsU#`Zc
zU~l4)R03!h#^)H13JAt-fLri98(yQJf@-MP-s$as%gzrsxaX$c-&d7BB4FYfUe+V|
zJ|M3EIDvT*_A(P|kMV_AFy9<@y2kx$JulBEef(@$XZlp8&1kQnDCH3$u>grr04ouD
zC91(mCt`jQaRI#I9S~8d%mnBaBxa%#Ev!Nu1M6W?-QOQyl>jp!F$Wgc*7B955^!tS
bmqGLgz~{6BRIYwu00000NkvXXu0mjfmg!hD
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..7099d0039b48d34e8799920e4e737e7f85f11042
GIT binary patch
literal 405
zc$@*10c!q<P)<h;3K|Lk000e1NJLTq000yK000mO1^@s678qX}00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0V+vEK~y+TV;~m1
z+@d9Me}hKg-Bn8bP$2@K^dB#jg#oV4lU9AW*8bzC%gg@X-xB}%;v`91s1)e{WZ3PE
zo^PM-3H*QO(1QQ7nv?(EUl#i5Ot~PDE=2+`!@fQ0{r~A^&Hwvry#C+aJN5tD!+rmc
z7qR(4l@SM!4g2<}^Z&Pp&Hq1MFZh3NrOp4d^|GH%W-&-Wl@bQn4Ey?^_WzfAW&dw4
zGJJckiB}b>lrR_}hW+1f&+>SS=WVJO1_F0hX&B$y>iFmNwT6Gh7<NHR>pwOOm1bbL
zxkw@J?a7J2bmsK`)uoz$gj|NrFc5}HGcc^FS3mUi`nLbC548Wk(xLU|+ARG$BzqGX
z1X^-`SzaQ4_;dqb?uiO6<NfsvEKsRo2N)OtF{~?M>m3;700000NkvXXu0mjfr4+j#
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c325b0fa2743e6f6ac1ed8a98cfe6c94d92bb94f
GIT binary patch
literal 379
zc$@)y0fhdEP)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0T4+<K~y+TV;}{*
z+@d9Me}hKg-Bn8bP$9~})p^pY57*j%{B(KQ|NC3wKVO_AX$zGiAAk(Kz0vdS(>;Oz
z?;Kk2e^zty|NF~AKb<KTB;VyQL%%)h{r~A^&Hwvry#C+aJN5tD!+rmc7qR(4m5~OJ
z4gL10^Z&Pp&Hq1MFZh3NrOp4d^|GH%W-&-Wl@brI8T$1>?f)<L%KqP8Wcc=66R#>%
zDe+*08~T60J<H=Qp0}xLC<xqHrD1$!tJa^>+v@*O%}@}yxkw@J?a7J2bn5i~)TWw$
zR526;*3_#X`g(oa|JMiF|6i%s`g3iT{vC=91%W_I?k~$r<PV>2;LANx!DS2;9x;G{
Z0RZ6|4ppka6R!XO002ovPDHLkV1n#Au?_$L
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a4084ea806d97603b9c51167d2500692a3a48167
GIT binary patch
literal 412
zc$@*80b~A&P)<h;3K|Lk000e1NJLTq000yK000mO1^@s678qX}00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0WnELK~y+TV;~m1
z+@d9Me}hKg-Bn8bP$2@K^dB#jg#oV4lU9AW*8bzC%gg@X-xB}%;v`91s1)e{WZ3PE
zo^PM-3H*QO(1QQ7nv?(EUl#i5Ot~PDE=2+`!@fQ0{r~A^&Hwvry#C+aJN5tD!+rmc
z7qR(4l@SM!4g2<}^Z&Pp&Hq1MFZh3NrOp4d^|GH%W-&-Wl@bQn4Ey?^_WzfAW&dw4
zGJJckiB}b>lrR`ZhJlh<>5u<lj1K<qw`Y00#q&1FF2iNuomCpfx3)U|d3~+nA5n(E
z3_`~+aC4DD-rJKCf$7ZY|Eo(i|A=-OI);HY_3DSdUf=fr^?~;PS30!*T$`nTho}fZ
zHwXp-ExEreFOffdx`8kEL<N`e{(1%$s1$yH%fLYl7#IL`sY9-g?o1y50000<MNUMn
GLSTX~$-e>s
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..682a501758e923dd75a95214c1f9c3fc33366d21
GIT binary patch
literal 466
zc$@*!0WJQCP)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0cS}>K~y+TV;}`g
znKDIU^5n^Z9UUF~P$9}eQ&W@btXZ=@o;!E$|NQy$KiAgQ+Crts2OvWiE?oF_*|KH-
z_wV2TzoDT4Wca7@@^bQB4m0%3nKS<n9Xj-X&YU^_ckS8*wjet@+YhRYG=OaA>C>nG
zpFDZ;|K7cO|4*AX?SFlJ{im#~ED5Mm;sG{8j~zSq|M20%|A7(swzRZV6{?hYK#ZY|
z?Q86uI@Z-Ywyxa|rh$B@cI<!{LtzFwcdz>o!!Qe=#-IXXU5acd2p}7d;c{RsvjCIO
zZ9-uRauv)Vbb~<{!_^ZfPBd<8Z2Yro)vAB^A__eS&<zI3LzO}RFq`Iq(kU=^{a><V
z$v<p{f&jXUojZ`&K#bwy!otEs*REarzkBy?P#XQy)6;VY-B1u9IgBkVEWTu9WE?Ij
zDap;t%QFTV&H|Of03cVP2MLIe;aYM4$Yn4Kz%;r`X#*G-08`7cwB1dl@Bjb+07*qo
IM6N<$f>jaAzW@LL
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e6e3966707b9196043d312f42a32f12325ed8410
GIT binary patch
literal 476
zc$@*;0VDp2P)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0dYx0K~y+TV;}{*
z+@d9Me}hKg-Bn8bP$9~})p^pY57*j%{B(KQ|NC3wKVO_AX$zGiAAk(Kz0vdS(>;Oz
z?;Kk2e^zty|NF~AKb<KTB;VyQL%%)h{r~A^&Hwvry#C+aJN5tD!+rmc7qR(4m5~OJ
z4gL10^Z&Pp&Hq1MFZh3NrOp4d^|GH%W-&-Wl@brI8T$1>?f)<L%KqP8Wcc=66R#>%
zDe-_9Lmk`K*g18qt9NW&yB|yg`B3fH0WpTc40P^Z_aBB~7C?<b1;n}(*-#KbHXOs{
z|M%OoJl^7Yn^2g7Tm>@--Cz*LaP^&48pc<)YW+FAt^Oarh(Zqnbb~?iP^A!XbCE*c
z+mjQ4>D1}}sZBNiuo(&h=q`5dKw<+ihKtwKs~`G$ecS)n2ipH%sn_~*ZI=EWbVEUa
z<S-7j<o>d}ME>yU2EN=A6<o$pVXOe;D)b-$@iAOW4gk3fW&xN+cPVWE0|NlU?ys4K
SFl4U)0000<MNUMnLSTZEh}?Ss
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..12e5adbf458f2485407b319fb3157469a5756a6d
GIT binary patch
literal 434
zc%17D@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|*pj^6T^Rm@
z;DWu&Cj&(|3p^r=f!eQwFr$;k><XYDdx@v7EBj404q;)&-7~{ffu<OFx;TbdoIZPL
zqu1d8kz*hA_vS85$+nJYYT<r0scAvCPz^)*{3ZGq)_Q+)ic@UvS77NW?0KtjkVlB;
zj!x^POVeiGkydYCaN_K<ndd(J=5JrT@ZgSh)n_dCYj!;<G@7}Falz8A^vyRV6;-z7
z{yu2XbN*#X6*t2yEi<0SOE16t;=Wk0=6-wTtl3AC3Y|hWrbhmaTi>qYX&~2cD8bV=
z?R4sjm5)}fQdoMVY`3e-@e`kG4og_)n;vxHDA^s$p%}LN?z>Z^vCBXLE`eLTE`PaI
zIOltO{|%P^941G%HaxF<`?v0YTI98rTI+0srlpEAteCPX=;&64=XaJ(nWWLRWs!!K
zC=bKi_pOFy_A-2qnyvyYlLS6ZeE#!p{0=dW!yi5sSlGPW^{(*T{L`Ci=AM79`tNTI
ZWBqA{&9_6X76Aj4!PC{xWt~$(6974Hy4C;y
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..cdf915d5e4df91e68118a7614585b9da6be2ec63
GIT binary patch
literal 646
zc$@)-0(t$3P)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0vkz0K~y+T)sxF>
z6Hye#KcS#IS3cH41;LGU6BM;BbfF-Ke}LOyXkZ{QbumnmRn(*<CJh)<LTJn)x^dwv
zF$NQegv6+uh*W%SDoW8dDwvsjkLSi|Nu_OUUHQS~aPGP1H{aX`HllbuzB?9+oe70P
z+wG};S1=eHP9~EJtyYWD>2yce^}}{)^HR`ECNrPQ<)~aPli%-G@I{}`w|VDF&}Oqq
zl}d$DsT36oh295TF4t*$*~ZilZ8REGuh*$mDp4YlAiu6Jw#N4y5g}w`5c>$&Vz;fY
z)u6RnjjGiu@d3_HXy^9JOYoUF%*qfh$T4iSTi4bg=(wisl>r>GrtqwD2ZG~MY2e_x
zt_MNAUhgm&*d`*F{&WkX90PskC?=3n^EPIs{syYnvD;(T3|`FL1|2;B`pD6`f#(b!
z?ScBmr#!@0Xm%j<B6f*5ySodT<1wnh991r+kmN1d*#j;q8jX$w0s$+Z&&#!-Y7b=!
zC%SojpbF^wV^EQ|;s-~ScW>goypH25_L`rmTisKBU6jpc<)2U`<wjh?Wal0PJpp}r
z2s(cj^p&H^7w*I2dW8RKhus2bnpSDI+f*zT$uJBn91g$khbl=Or@E7PM}^-Ge?9`e
zoMERNRZb5;=2g6911Iekz~OLwb2^<>kH<6ScDqLyK4h1cCH%Nz2Jy-a<Bb_vWvF?s
g3j>$!mj6b;FQ&-AtsZH$djJ3c07*qoM6N<$f_i5iBLDyZ
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2a70ddb6b0398415db87d3fb6ae856d3970fa8f9
GIT binary patch
literal 383
zc$@)$0f7FAP)<h;3K|Lk000e1NJLTq000yK000mO1^@s678qX}00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0Tf9@K~y+TV;~kx
znKDIU^5n^Z9UUF~P$BX`Q&W@btXZ=@o;!E$|Dr{UKG)XP+CrsB2Oz^1E?oF_*|KH-
z_wV2TzoDVw|NQy$Kb4o4lkQTOVQ0>q`G4rpq5pH{%=y1-*DkQZ+1c5CP-VmcWW!FM
zKK=jX$&>&0?%n%;+O%o^>+9=3Wo2baK$Q{(*bF;%?AZUqhY$Y;M$FsN(o$8ZQo>+_
z7zT_r7GP4iO%=mHVB*Ay#=wO82WY@QVhp>WrS%^hhDtLq0JBpbD4hWf1tpDtgj|Nr
zFc5}HGcXhu79P5G?b`p{yLbNwhUK50o}N1-dlMO0SXg|?$jCTcQc{wemzQS@Ot>si
dsbL2g7ytn@D+d_(rHlXo002ovPDHLkV1l}3q;LQL
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a1c3a3528f20175d88862d654c78fd0226122238
GIT binary patch
literal 369
zc$@)o0gnEOP)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0R~A#K~y+TV;}`g
znKDIU^5n^Z9UUF~P$9}eQ&W@btXZ=@o;!E$|NQy$KiAgQ+Crts2OvWiE?oF_*|KH-
z_wV2TzoDT4Wca7@@^bQB4m0%3nKS<n9Xj-X&YU^_ckS8*wjet@+YhRYG=OaA>C>nG
zpFDZ;|K7cO|4*AX?SFlJ{im#~ED5Mm;sG{8j~zSq|M20%|A7(swzRZV6{?hYFv1N5
z#xe^q3Eifup&&4E;zZ-d#>PLZR;~I+HA6uFm`(FQ=@gi|{x4aw<R4WG1%blC!b8`t
zUHiX#_ij)c{nOLabBAI>LBPVo;!8$G#^I8ZlH9z!JY%5YEKsQt0T>tnarY2#bh?ah
P00000NkvXXu0mjfW?iGm
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..b7a69a0d68f7a0457b3aa0fc2ad386bcdd226d55
GIT binary patch
literal 392
zc$@)<0eAk1P)<h;3K|Lk000e1NJLTq000yK000mO1^@s678qX}00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0Ub$1K~y+TV;~kx
znKDIU^5n^Z9UUF~P$BX`Q&W@btXZ=@o;!E$|Dr{UKG)XP+CrsB2Oz^1E?oF_*|KH-
z_wV2TzoDVw|NQy$Kb4o4lkQTOVQ0>q`G4rpq5pH{%=y1-*DkQZ+1c5CP-VmcWW!FM
zKK=jX$&>&0?%n%;+O%o^>+9=3Wo2baK$Q{(*bF;%?AZUqhY$Y;M$FsN(o$8ZQo>*u
z8CLp_m!b5>e=tS|z*u7eCWYH1y9}3s6DLkI1}5A;Km+~}Wf;sLbPNN)?34#eXFx+i
zN#h^UE<?vKP*_-a=-Rbw|99`+{T~>Xe|ma)?hq9L=mx=ng@whJjEs!KB_$=fd3kxp
mz=X>JmBJ5j890am0|Nj>F+)>CIL#~o0000<MNUMnLSTXc6RCFq
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..bb7b212615bc1382b1fe9b5ade219bd9c97eb2f3
GIT binary patch
literal 435
zc%17D@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|*pj^6T^Rm@
z;DWu&Cj&(|3p^r=f!eQwFr$;k><XYDdx@v7EBj404q*wd^ZUcnfTkFGx;TbdoK8+r
zVEm&wang_PPiLzhUZ4N_#<Rb_P8<LNmo?9>3%*%ZZ~x(bido#2zK7FqPj1hfv&}{N
zgH==P)cO9uS(N7++<!lvhv&N9$<H1J(z$!H4xK#jvL-EUp1jkUUCVfSp1+bb*{drt
z<7{%Jz0tqz=_+ByK5yptu^!%^`&-iPe%;TXa*xeT(}GNl4S=Ao>hOp6XWPsD|J9fq
zTYP;ksH{0l(!xUG-<_I&@7}HcE*8~Ze<v*g2$r}Qw=pis0<jy~56-s}7yEX8&+(a+
zbsoa2B~@kr?5uE_|4_bLNpn^p?~lKyX9e2z{XGBpze~0oWAzqQzQ3{cpZ?F^H1Gd?
zAD4eMn}v54pPlABb%I@mf*a?DTF&WDY}PA$%Dmb?pXd0#;=27W3MM?{KfHjQVeOW+
V7Eb?OWdTE!!PC{xWt~$(6986uzkdJ#
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..f7f52a30b335af47efc8ed05ee7748a9c5e05104
GIT binary patch
literal 453
zc$@*n0XqJPP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00001b5ch_0Itp)
z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01ejw01ejxLMWSf00007bV*G`2iXP)
z6A~S^gF7n#00BiwL_t(I%gxfiO9F8i$MMgd)@k@FlTdIG)5Rj99~2cDSl}2$^q({d
z{S6JR?m=yeL*!Nw2ceK5M_J+EDJp4r-VeXuriLSk@6pmTJ<IDoJUkpMOe>!V4t26I
z*3|3h;mZdA@T|C+3&#{PTk}V=mx4RlD4&M5nFG~aIOYm2+}WKzPphY)Ao4Cxa4Rn!
zPkU{wIFLyDC#KvO)vpkVC*k#lg!W_cmSe?$0Z<M4u!BxT#Goz@)ORV)TfvlSunCi(
zRP7R7US2yxle2;;Q6b-#x_zm(zLf1t0KokP{|H#crT{Ia><z{FG&7%`W5uRFcy2cr
zIm$;U8B<nl3Xsug(;3*awZtMvbd|DT0I+{AmAY_{S&e}NcJWrYAe@7~s<M>%4V!Lo
vl=6M<uXrD#?%uhf7&nSzSl&5-fqy?=S*h$cTTNKZ00000NkvXXu0mjf(4V}g
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..4e51c94d1fa0dbd2aacafe0f5c0d8bdee3f4c214
GIT binary patch
literal 505
zc$@+G0S5kwP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00001b5ch_0Itp)
z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01ejw01ejxLMWSf00007bV*G`2iXP)
z6BZDxUV@SU00DVPL_t(I%e|7lPr^VL#-HoQB5@%M;6+FWCk7KuL}-k&yOaL_J5I86
zW9d%P#J_;S(SgB8A|i&NvMGxp63b%ZM-Z;<orAU^gg{w*lUv?<e$QRr_aRuJEXztV
z#`3P~UX@CvcL0#0!j(#8OP1wFUDq!tr7wnI8~^}`3R6n&R8@_qQYkbV4P-Kz&u+K7
zsYHR#%H^l&o?EX&Rn_%Qr*pUz7Jvv2mn89(P747D!!SOI#bQ!f_H6*TZ@0t#UM`34
zwKXvsjdm@|a>yb$o6W|B5a67{_kH9G1pt6ANv{g$8vvlJ0*hbCMCSY?&?`}3-}f=K
zvQq+F2TYiQTCJu~N-yWZwr#%w@c*Re(~pJ*gTY`MfW|o&vv9xP-zR`V078JtTm#0K
zu4$TV+ct4sS4_jEY4(Cu&dw~Hf#W#Gi9`aLrlHwva#@z2hr{8KX_~!7SqTkVt=7(X
vJidv=Vl~Ftt><|U)oRrX?n%Vh|9|}eXD!-lw%t!)00000NkvXXu0mjfpfuRR
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-linux/enigmail-common.css
@@ -0,0 +1,415 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * Common Enigmail styles for all platforms
+ */
+
+
+.action-box {
+ width: 100px
+}
+
+#expandedEnigmailBox {
+ background-color: #d8e0e8;
+ color: black;
+}
+
+.enigmailHeaderName {
+ color: #888a85;
+ /* lower contrast, TB3 */
+ text-align: right;
+ background-color: transparent;
+ -moz-margin-end: 2px;
+}
+
+.enigmailHeaderNameBox {
+ width: 7.7em;
+ background-color: #d8e0e8;
+ color: black;
+}
+
+.enigmailHeaderValue {
+ min-width: 50px;
+ white-space: normal;
+ color: black !important;
+ line-height: 1.4em !important;
+
+ -moz-appearance: none !important;
+
+ padding: 0px !important;
+ margin: 0px !important;
+ -moz-margin-start: 3px !important;
+ border: none !important;
+ background-color: transparent !important;
+}
+
+.enigmailHeaderSpacer {
+ width: 2px;
+}
+
+.enigmailHeaderBoxLabelNoSignature {
+ background-color: #d8e0e8;
+ color: black;
+}
+
+.enigmailHeaderBoxLabelSignatureOk {
+ background-color: #ccffcc;
+ color: black;
+}
+
+.enigmailHeaderBoxLabelSignatureVerified {
+ background-color: #ccffff;
+ color: black;
+}
+
+.enigmailHeaderBoxLabelSignatureNotOk {
+ background-color: #FFD8FE;
+ color: black;
+}
+
+.enigmailHeaderBoxLabelSignatureUnknown {
+ background-color: #FFFFC9;
+ color: black;
+}
+
+.enigmailHeaderBoxLabelBuggyMailFormat {
+ background-color: #FFD8FE;
+ color: black;
+}
+
+.enigmailLink {
+ text-decoration: underline;
+ color: blue;
+ cursor: pointer;
+}
+
+.enigmailGroupbox {
+ border-radius: 4px;
+ border-style: groove;
+ border-width: thin;
+ padding: 3px;
+ margin: 3px;
+ border-color: lightgrey;
+}
+
+.enigmailCaptionboxNoTitle {
+ border: 2px groove threedface;
+ padding: 0.5em;
+ margin: 1em 2px;
+ border-radius: 4px;
+}
+
+.enigmailCaptionbox {
+ border: 2px groove threedface;
+ border-top: none;
+ padding: 0.5em;
+ margin: 1em 2px;
+ border-radius: 4px;
+}
+
+.enigmailCaptionbox>h1 {
+ font: 1em normal;
+ margin: -1em -0.5em 0;
+ border-radius: 4px;
+}
+
+.enigmailCaptionbox>h1>span {
+ float: left;
+ border-radius: 4px;
+}
+
+.enigmailCaptionbox>h1:before {
+ border-top: 2px groove threedface;
+ content: ' ';
+ float: left;
+ margin: 0.5em 2px 0 -1px;
+ width: 0.75em;
+ border-radius: 4px;
+}
+
+.enigmailCaptionbox>h1:after {
+ border-top: 2px groove threedface;
+ content: ' ';
+ display: block;
+ height: 1.5em;
+ left: 2px;
+ margin: 0 1px 0 0;
+ overflow: hidden;
+ position: relative;
+ top: 0.5em;
+ border-radius: 4px;
+}
+
+.enigmailStrong {
+ font-weight: bold;
+ color: red;
+}
+
+.enigmailUidInactive {
+ color: gray;
+ font-style: italic;
+}
+
+.enigMsgHdrView-flat-button {
+ min-width: 1px !important;
+ -moz-appearance: none !important;
+ color: black !important;
+ background-color: #DDDDDD !important;
+
+ border: 2px solid transparent;
+ margin-top: 2px !important;
+ margin-bottom: 2px !important;
+ padding: 0 2px;
+ border-color: ThreeDShadow;
+ border-width: 1px;
+ min-height: 1ex;
+}
+
+.enigMsgHdrView-flat-button:hover {
+ background-color: #CCCCCC !important;
+}
+
+.enigmailToolbar {
+ -moz-appearance: none;
+}
+
+.enigmailTitle {
+ font-size: larger;
+ font-weight: bold;
+}
+
+.enigmailDisabled {
+ color: #a6a6a6;
+ /* grey */
+}
+
+#messagepanebox[enigSigned="ok"] #messagepane {
+ color: red;
+}
+
+#expandedautocryptRow {
+ visibility: collapse;
+}
+
+.enigmailMessagePane {
+ margin: 6px;
+ -moz-user-focus: normal;
+ -moz-user-select: text;
+ cursor: text !important;
+ white-space: pre-wrap;
+ unicode-bidi: -moz-plaintext;
+}
+
+/***************************************************
+ * Various other styles
+ ***************************************************/
+
+.enigmailDialogTitle {
+ font-size: 120%;
+ font-weight: bold;
+ padding-bottom: 6px;
+}
+
+.enigmailDialogBody {
+ -moz-user-focus: normal;
+ -moz-user-select: text;
+ cursor: text !important;
+ white-space: pre-wrap;
+ unicode-bidi: -moz-plaintext;
+}
+
+.enigmailDialogInfoBox {
+ border: 1px solid #afafaf;
+ border-radius: 5px;
+ padding: 15px;
+ margin: 0px 10px 0px 10px;
+}
+
+.enigmailPrefsTitle {
+ font-weight: bold;
+ height: 25px;
+}
+
+.enigmailKeyImportHeader {
+ font-weight: bold;
+ color: #888;
+}
+
+.enigmailKeyImportUserId {
+ font-weight: bold;
+}
+
+.enigmailKeyImportCaption {
+ list-style-image: url("chrome://openpgp/skin/importSuccess.png");
+ max-height: 2em;
+ max-width: 2em;
+}
+
+.enigmailErrorIcon {
+ list-style-image: url("chrome://openpgp/skin/password-error.svg");
+ padding-top: 5px;
+ padding-bottom: 5px;
+ height: 2.2em;
+ width: 2.2em;
+}
+
+.enigmailKeyImportDetails {
+ color: blue;
+}
+
+.enigmailKeyImportDetails:hover {
+ text-decoration: underline;
+}
+
+treechildren::-moz-tree-cell-text(enigmailSubkeyTitle) {
+ font-weight: bold;
+}
+
+treechildren::-moz-tree-cell-text(enigmailOwnKey) {
+ font-weight: bold;
+}
+
+treechildren::-moz-tree-cell-text(enigKeyInactive) {
+ color: gray;
+ font-style: italic;
+}
+
+treechildren::-moz-tree-column(enigDontEncrypt) {
+ background-color: rgb(90%, 90%, 90%);
+}
+
+treechildren::-moz-tree-cell-text(fixedWidthFont) {
+ font-family: "Courier New", Courier, monospace;
+}
+
+.enigmailExpandViewButton {
+ width: 9px;
+ /* The image's width is 9 pixels */
+ list-style-image: url("chrome://openpgp/skin/twisty-clsd.png");
+}
+
+.enigmailCollapseViewButton {
+ width: 9px;
+ /* The image's width is 9 pixels */
+ list-style-image: url("chrome://openpgp/skin/twisty-open.png");
+
+}
+
+.enigmailWarningIcon {
+ list-style-image: url("chrome://openpgp/skin/warning-16.png");
+}
+
+treechildren::-moz-tree-image(enigSignedEncrypted) {
+ list-style-image: url("chrome://openpgp/skin/col-encrypted-signed.png");
+}
+
+treechildren::-moz-tree-image(enigSigned) {
+ list-style-image: url("chrome://openpgp/skin/enigSignOk.png");
+}
+
+treechildren::-moz-tree-image(enigEncrypted) {
+ list-style-image: url("chrome://openpgp/skin/enigEncOk.png");
+}
+
+/*
+ the following styles are available for the key trust
+ columnm in the key manager:
+ enigmail_keyValid_unknown
+ enigmail_keyValid_invalid
+ enigmail_keyValid_disabled
+ enigmail_keyValid_revoked
+ enigmail_keyValid_expired
+ enigmail_keyTrust_untrusted
+ enigmail_keyTrust_marginal
+ enigmail_keyTrust_full
+ enigmail_keyTrust_ultimate
+ enigmail_keyTrust_unknown
+
+They can be applied using:
+treechildren::-moz-tree-cell(STYLE) {}
+treechildren::-moz-tree-cell-text(STYLE) {}
+*/
+
+/******************************
+ * Rules for filter actions
+ ******************************/
+
+.ruleactiontarget[type="enigmail@enigmail.net#filterActionMoveDecrypt"] {
+ -moz-binding: url("chrome://messenger/content/searchWidgets.xml#ruleactiontarget-folder") !important;
+}
+
+.ruleactiontarget[type="enigmail@enigmail.net#filterActionCopyDecrypt"] {
+ -moz-binding: url("chrome://messenger/content/searchWidgets.xml#ruleactiontarget-folder") !important;
+}
+
+.ruleactiontarget[type="enigmail@enigmail.net#filterActionEncrypt"] {
+ -moz-binding: url("chrome://messenger/content/searchWidgets.xml#ruleactiontarget-forwardto") !important;
+}
+
+.enigmailPassphraseQuality {
+ margin: 2px 4px;
+ min-width: 128px;
+ height: 12px;
+ background-color: #C21540;
+}
+
+.enigmailPassphraseQuality[value="medium"] {
+ background-color: #5885C4;
+}
+
+.enigmailPassphraseQuality[value="high"] {
+ background-color: #64C4A1;
+}
+
+.enigmailPassphraseQuality[value="excellent"] {
+ background-color: #1A9C2A;
+}
+
+/* a spinning wheel circle */
+.enigmailWheel {
+ list-style-image: url("chrome://openpgp/skin/spinning-wheel.png");
+ max-width: 100%;
+ max-height: 100%;
+}
+
+.enigmailSpinning {
+ animation: enigmailDoRotation 1.4s infinite linear;
+ transform: translateZ(0);
+}
+
+@keyframes enigmailDoRotation {
+ 0% {
+ transform: rotate(0deg);
+ }
+
+ 100% {
+ transform: rotate(360deg);
+ }
+}
+
+/***************************************************
+ * Styles for Setup Wizard and Key Generation
+ ***************************************************/
+
+#passphraseBox #passphraseError {
+ margin-top: 3px;
+ margin-bottom: 3px;
+ color: red;
+}
+
+#passphraseBox #passphraseErrorRepeat {
+ margin-top: 3px;
+ margin-bottom: 3px;
+ color: red;
+}
+
+.enigmailOkSign {
+ content: url("chrome://openpgp/skin/ok-sign.svg");
+ padding-left: 5px;
+ padding-right: 5px;
+ height: 1.3em;
+}
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-linux/enigmail-html.css
@@ -0,0 +1,101 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * Enigmail style for HTML pages, displayed in tabs
+ */
+
+
+
+
+body {
+ color: black;
+ padding: 0;
+ background-color: white;
+ border-style: none;
+ font-family: Arial, Helvetica, sans-serif;
+ font-size: 14px;
+}
+
+h1 {
+ font-size: 2em;
+ color: #0077bb;
+}
+
+h2 {
+ font-size: 1.4em;
+ margin-top: 1.3em;
+}
+
+h3 {
+ font-size: 1.2em;
+ margin-top: 1.3em;
+}
+
+ul {
+ list-style-image: none;
+}
+
+a {
+ text-decoration: underline;
+}
+
+a:link {
+ color: blue;
+}
+
+a:visited {
+ color: #9900cc;
+}
+
+a:hover {
+ color: red;
+}
+
+.header-bar {
+ box-sizing: border-box;
+ font-size: 14px;
+ font-weight: 300;
+ line-height: 16.8px;
+ margin-left: 0px;
+ margin-right: 0px;
+ padding-left: 15px;
+ padding-right: 15px;
+ position: relative;
+ min-height: 80px;
+ background-color: #0077bb;
+}
+
+.header-icon {
+ box-sizing: border-box;
+ float: left;
+ font-size: 14px;
+ line-height: 16.8px;
+ margin-left: 0px;
+ margin-right: 10px;
+}
+
+.spacer {
+ padding-top: 40px;
+}
+
+.body {
+ max-width: 800px;
+}
+
+.logo-img {
+ max-height: 90px;
+}
+
+button {
+ font-size: 1.2em;
+ margin-top: 1.3em;
+}
+
+.hidden {
+ visibility: collapse;
+ max-height: 0px;
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-linux/enigmail.css
@@ -0,0 +1,288 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * Enigmail styles for Linux
+ */
+
+@import url("chrome://openpgp/skin/enigmail-common.css"); /* common styles for all platforms */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+
+#button-enigmail-decrypt
+ {
+ list-style-image : url("chrome://openpgp/skin/decrypt-active-18.svg");
+ }
+
+#button-enigmail-decrypt[disabled]
+ {
+ list-style-image : url("chrome://openpgp/skin/decrypt-inactive-18.svg");
+ }
+
+
+/***************************************************
+ * Icons for compose toolbar: encryption
+ ***************************************************/
+
+#button-enigmail-encrypt
+{
+ list-style-image : url("chrome://openpgp/skin/encrypt-inactive-18.svg");
+}
+
+/* hide label unless text-only mode is enabled */
+toolbar:not([mode="text"]) #button-enigmail-encrypt .toolbarbutton-text {
+ display: none;
+}
+
+#button-enigmail-encrypt[disabled] {
+ list-style-image : url("chrome://openpgp/skin/encrypt-disabled-18.svg");
+}
+
+#button-enigmail-encrypt[encrypted="activeNone"]
+{
+ list-style-image : url("chrome://openpgp/skin/encrypt-active-18.svg");
+}
+
+#button-enigmail-encrypt[encrypted="forceYes"]
+{
+ list-style-image : url("chrome://openpgp/skin/encrypt-active-18.svg");
+}
+
+#button-enigmail-encrypt[encrypted="forceNo"]
+{
+ list-style-image : url("chrome://openpgp/skin/encrypt-inactive-18.svg");
+}
+
+
+/***************************************************
+ * Icons for compose toolbar: signing
+ ***************************************************/
+
+#button-enigmail-sign
+{
+ list-style-image : url("chrome://openpgp/skin/sign-inactive-18.svg");
+}
+
+/* hide label unless text-only mode is enabled */
+toolbar:not([mode="text"]) #button-enigmail-sign .toolbarbutton-text {
+ display: none;
+}
+
+#button-enigmail-sign[disabled] {
+ list-style-image : url("chrome://openpgp/skin/sign-disabled-18.svg");
+}
+
+#button-enigmail-sign[signed="activeNone"]
+{
+ list-style-image : url("chrome://openpgp/skin/sign-active-18.svg");
+}
+
+#button-enigmail-sign[signed="forceYes"]
+{
+ list-style-image : url("chrome://openpgp/skin/sign-active-18.svg");
+}
+
+#button-enigmail-sign[signed="forceNo"]
+{
+ list-style-image : url("chrome://openpgp/skin/sign-inactive-18.svg");
+}
+
+/***************************************************
+ * Icons for compose toolbar: attach own key
+ ***************************************************/
+
+#button-enigmail-attach
+{
+ list-style-image : url("chrome://openpgp/skin/attach-inactive-18.svg");
+}
+
+#button-enigmail-attach[disabled] {
+ list-style-image : url("chrome://openpgp/skin/attach-disabled-18.svg");
+}
+
+#button-enigmail-attach[addPubkey="true"]
+{
+ list-style-image : url("chrome://openpgp/skin/attach-active-18.svg");
+}
+
+
+/***************************************************
+ * Icons for compose toolbar: Protect header
+ ***************************************************/
+
+#enigmail-toolbar-encryptHdr-button
+{
+ list-style-image : url("chrome://openpgp/skin/headerUnprotected-18.svg");
+}
+
+#enigmail-toolbar-encryptHdr-button[disabled] {
+ list-style-image : url("chrome://openpgp/skin/headerUnprotected-18.svg");
+}
+
+#enigmail-toolbar-encryptHdr-button[checked="true"]
+{
+ list-style-image : url("chrome://openpgp/skin/headerProtected-18.svg");
+}
+
+
+/***************************************************
+ * Icons for messenger status bar
+ ***************************************************/
+
+#enigmail-status-bar #enigmail-signed-status {
+ list-style-image: none;
+ visibility: collapse;
+}
+
+#enigmail-status-bar[signed="ok"] #enigmail-signed-status {
+ list-style-image: url("chrome://openpgp/skin/enigSignOk.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[signed="notok"] #enigmail-signed-status {
+ list-style-image: url("chrome://openpgp/skin/enigSignNotOk.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[signed="unknown"] #enigmail-signed-status {
+ list-style-image: url("chrome://openpgp/skin/enigSignUnkown.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[signed="inactive"] #enigmail-signed-status {
+ list-style-image: url("chrome://openpgp/skin/enigSignInactive.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar #enigmail-encrypted-status {
+ list-style-image: none;
+ visibility: collapse;
+}
+
+#enigmail-status-bar[encrypted="ok"] #enigmail-encrypted-status {
+ list-style-image: url("chrome://openpgp/skin/enigEncOk.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[encrypted="notok"] #enigmail-encrypted-status {
+ list-style-image: url("chrome://openpgp/skin/enigEncNotOk.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[encrypted="inactive"] #enigmail-encrypted-status {
+ list-style-image: url("chrome://openpgp/skin/enigEncInactive.png");
+ visibility: visible;
+}
+
+
+/***************************************************
+ * Icons for compose status bar: signed states
+ ***************************************************/
+
+#enigmail-status-bar[signed="forceYes"] #enigmail-signed-status {
+ list-style-image: url("chrome://openpgp/skin/enigSignForceYes.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[signed="forceNo"] #enigmail-signed-status {
+ list-style-image: url("chrome://openpgp/skin/enigSignForceNo.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[signed="inactiveNone"] #enigmail-signed-status {
+ list-style-image: url("chrome://openpgp/skin/enigSignInactiveNone.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[signed="inactivePlus"] #enigmail-signed-status {
+ list-style-image: url("chrome://openpgp/skin/enigSignInactivePlus.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[signed="inactiveMinus"] #enigmail-signed-status {
+ list-style-image: url("chrome://openpgp/skin/enigSignInactiveMinus.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[signed="inactiveConflict"] #enigmail-signed-status {
+ list-style-image: url("chrome://openpgp/skin/enigSignInactiveConflict.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[signed="activeNone"] #enigmail-signed-status {
+ list-style-image: url("chrome://openpgp/skin/enigSignActiveNone.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[signed="activePlus"] #enigmail-signed-status {
+ list-style-image: url("chrome://openpgp/skin/enigSignActivePlus.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[signed="activeMinus"] #enigmail-signed-status {
+ list-style-image: url("chrome://openpgp/skin/enigSignActiveMinus.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[signed="activeConflict"] #enigmail-signed-status {
+ list-style-image: url("chrome://openpgp/skin/enigSignActiveConflict.png");
+ visibility: visible;
+}
+
+/***************************************************
+ * Icons for compose status bar: encrypted states
+ ***************************************************/
+
+#enigmail-status-bar[encrypted="forceYes"] #enigmail-encrypted-status {
+ list-style-image: url("chrome://openpgp/skin/enigEncForceYes.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[encrypted="forceNo"] #enigmail-encrypted-status {
+ list-style-image: url("chrome://openpgp/skin/enigEncForceNo.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[encrypted="activeNone"] #enigmail-encrypted-status {
+ list-style-image: url("chrome://openpgp/skin/enigEncActiveNone.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[encrypted="activePlus"] #enigmail-encrypted-status {
+ list-style-image: url("chrome://openpgp/skin/enigEncActivePlus.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[encrypted="activeMinus"] #enigmail-encrypted-status {
+ list-style-image: url("chrome://openpgp/skin/enigEncActiveMinus.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[encrypted="activeConflict"] #enigmail-encrypted-status {
+ list-style-image: url("chrome://openpgp/skin/enigEncActiveConflict.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[encrypted="inactiveNone"] #enigmail-encrypted-status {
+ list-style-image: url("chrome://openpgp/skin/enigEncInactiveNone.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[encrypted="inactivePlus"] #enigmail-encrypted-status {
+ list-style-image: url("chrome://openpgp/skin/enigEncInactivePlus.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[encrypted="inactiveMinus"] #enigmail-encrypted-status {
+ list-style-image: url("chrome://openpgp/skin/enigEncInactiveMinus.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[encrypted="inactiveConflict"] #enigmail-encrypted-status {
+ list-style-image: url("chrome://openpgp/skin/enigEncInactiveConflict.png");
+ visibility: visible;
+}
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-linux/headerProtected-18.svg
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ viewBox="0 0 56.514998 56.514999"
+ version="1.1"
+ id="svg8"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06"
+ sodipodi:docname="headerProtected-18.svg"
+ inkscape:export-xdpi="2.27"
+ inkscape:export-ydpi="2.27">
+ <defs
+ id="defs2">
+ <linearGradient
+ id="linearGradient8317"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#ff0000;stop-opacity:1;"
+ offset="0"
+ id="stop8315" />
+ </linearGradient>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="18"
+ inkscape:cx="16.413862"
+ inkscape:cy="10.472223"
+ inkscape:document-units="mm"
+ inkscape:current-layer="layer3"
+ showgrid="false"
+ inkscape:snap-to-guides="true"
+ inkscape:snap-grids="true"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1239"
+ inkscape:window-height="755"
+ inkscape:window-x="41"
+ inkscape:window-y="1"
+ inkscape:window-maximized="1"
+ fit-margin-top="0"
+ fit-margin-left="-0.8"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ units="px"
+ scale-x="7.4">
+ <sodipodi:guide
+ position="3883.8708,741.49345"
+ orientation="0,1"
+ id="guide4697"
+ inkscape:locked="false" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata5">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="Layer 2"
+ style="display:inline"
+ transform="translate(-46.896674,-179.42294)">
+ <g
+ id="g6697"
+ transform="matrix(1,0,0,0.95416667,0,10.813822)">
+ <image
+ width="29.903757"
+ height="10.697155"
+ preserveAspectRatio="none"
+ style="image-rendering:optimizeQuality"
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZ0AAAAlCAYAAACZMTQOAAAABHNCSVQICAgIfAhkiAAAANhJREFU eJzt1bFtgwAAAMEYUSQFFBEzMg5LsEVGYBZE78qZIo8U303w3T+WZXmd5/kBAH9pXdefwXAAKFzX 9TXcHQHA+zAdADKmA0DGdADImA4AGdMBIGM6AGRMB4CM6QCQMR0AMqYDQMZ0AMiYDgAZ0wEgYzoA ZEwHgIzpAJAxHQAypgNAxnQAyJgOABnTASBjOgBkTAeAjOkAkDEdADKmA0DGdADImA4AGdMBIGM6 AGRMB4CM6QCQGeZ5vrsBgDcwTdPzsW3b93Ecn3fHAPB/jeP42vf9/AWoNBUXx/LG4QAAAABJRU5E rkJggg== "
+ id="image4638"
+ x="60.220459"
+ y="185.83905" />
+ <path
+ sodipodi:nodetypes="cccccccccccccccccccc"
+ inkscape:connector-curvature="0"
+ id="path4569"
+ d="m 53.961054,182.24537 c 0,-1.18467 -0.02564,-3.06908 3.689545,-3.06908 7.699536,0 24.843888,-3.1e-4 35.936576,0 2.673147,0 2.673147,3.3698 2.673147,3.3698 0,0 0.08347,9.53394 0.08488,21.8929 l 0.0021,20.50537 -4.089856,4.26283 -4.089848,4.26285 -31.646457,-0.0175 c -2.559641,0 -2.559641,-3.32743 -2.559641,-3.32743 V 182.2453 m 31.760891,40.60948 3.825229,-0.015 4.234526,0.015 v -40.03579 l -36.85476,-0.002 v 47.70131 c 10.424891,-0.0325 16.911425,-0.0246 28.795002,-0.0246 v -7.63872 z"
+ style="fill:#000000;stroke-width:0.42511523" />
+ <image
+ width="29.903757"
+ height="2.6499548"
+ preserveAspectRatio="none"
+ style="image-rendering:optimizeQuality"
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZ0AAAAlCAYAAACZMTQOAAAABHNCSVQICAgIfAhkiAAAANhJREFU eJzt1bFtgwAAAMEYUSQFFBEzMg5LsEVGYBZE78qZIo8U303w3T+WZXmd5/kBAH9pXdefwXAAKFzX 9TXcHQHA+zAdADKmA0DGdADImA4AGdMBIGM6AGRMB4CM6QCQMR0AMqYDQMZ0AMiYDgAZ0wEgYzoA ZEwHgIzpAJAxHQAypgNAxnQAyJgOABnTASBjOgBkTAeAjOkAkDEdADKmA0DGdADImA4AGdMBIGM6 AGRMB4CM6QCQGeZ5vrsBgDcwTdPzsW3b93Ecn3fHAPB/jeP42vf9/AWoNBUXx/LG4QAAAABJRU5E rkJggg== "
+ id="image4649"
+ x="60.220459"
+ y="203.46257" />
+ <image
+ width="29.903757"
+ height="2.6499548"
+ preserveAspectRatio="none"
+ style="image-rendering:optimizeQuality"
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZ0AAAAlCAYAAACZMTQOAAAABHNCSVQICAgIfAhkiAAAANhJREFU eJzt1bFtgwAAAMEYUSQFFBEzMg5LsEVGYBZE78qZIo8U303w3T+WZXmd5/kBAH9pXdefwXAAKFzX 9TXcHQHA+zAdADKmA0DGdADImA4AGdMBIGM6AGRMB4CM6QCQMR0AMqYDQMZ0AMiYDgAZ0wEgYzoA ZEwHgIzpAJAxHQAypgNAxnQAyJgOABnTASBjOgBkTAeAjOkAkDEdADKmA0DGdADImA4AGdMBIGM6 AGRMB4CM6QCQGeZ5vrsBgDcwTdPzsW3b93Ecn3fHAPB/jeP42vf9/AWoNBUXx/LG4QAAAABJRU5E rkJggg== "
+ id="image4671"
+ x="60.220459"
+ y="211.76212" />
+ <image
+ width="23.725101"
+ height="2.6499548"
+ preserveAspectRatio="none"
+ style="image-rendering:optimizeQuality"
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZ0AAAAlCAYAAACZMTQOAAAABHNCSVQICAgIfAhkiAAAANhJREFU eJzt1bFtgwAAAMEYUSQFFBEzMg5LsEVGYBZE78qZIo8U303w3T+WZXmd5/kBAH9pXdefwXAAKFzX 9TXcHQHA+zAdADKmA0DGdADImA4AGdMBIGM6AGRMB4CM6QCQMR0AMqYDQMZ0AMiYDgAZ0wEgYzoA ZEwHgIzpAJAxHQAypgNAxnQAyJgOABnTASBjOgBkTAeAjOkAkDEdADKmA0DGdADImA4AGdMBIGM6 AGRMB4CM6QCQGeZ5vrsBgDcwTdPzsW3b93Ecn3fHAPB/jeP42vf9/AWoNBUXx/LG4QAAAABJRU5E rkJggg== "
+ id="image4682"
+ x="60.220459"
+ y="220.06163" />
+ <rect
+ style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.87403107;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect6711"
+ width="2.3547916"
+ height="4.9358077"
+ x="63.249397"
+ y="188.49934" />
+ <rect
+ y="188.49934"
+ x="68.613091"
+ height="4.9358077"
+ width="2.3547916"
+ id="rect6766"
+ style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.87403107;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <rect
+ style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.87403107;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect6768"
+ width="2.3547916"
+ height="4.9358077"
+ x="74.238426"
+ y="188.49934" />
+ <rect
+ y="188.49934"
+ x="79.340477"
+ height="4.9358077"
+ width="2.3547916"
+ id="rect6772"
+ style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.87403107;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <rect
+ style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.87403107;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect6774"
+ width="2.3547916"
+ height="4.9358077"
+ x="84.965813"
+ y="188.49934" />
+ </g>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer3"
+ inkscape:label="Layer 1"
+ transform="translate(0,-4.4914085e-6)"
+ style="display:inline">
+ <rect
+ style="opacity:1;fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.48279953;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect6778"
+ width="50.889664"
+ height="12.689711"
+ x="3.0089004"
+ y="7.8493094" />
+ </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-linux/headerUnprotected-18.svg
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ viewBox="0 0 56.514998 56.514999"
+ version="1.1"
+ id="svg8"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06"
+ sodipodi:docname="headerUnprotected-18.svg"
+ inkscape:export-xdpi="2.27"
+ inkscape:export-ydpi="2.27">
+ <defs
+ id="defs2">
+ <linearGradient
+ id="linearGradient8317"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#ff0000;stop-opacity:1;"
+ offset="0"
+ id="stop8315" />
+ </linearGradient>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="18"
+ inkscape:cx="16.413862"
+ inkscape:cy="10.472223"
+ inkscape:document-units="mm"
+ inkscape:current-layer="layer3"
+ showgrid="false"
+ inkscape:snap-to-guides="true"
+ inkscape:snap-grids="true"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1239"
+ inkscape:window-height="755"
+ inkscape:window-x="41"
+ inkscape:window-y="1"
+ inkscape:window-maximized="1"
+ fit-margin-top="0"
+ fit-margin-left="-0.8"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ units="px"
+ scale-x="7.4">
+ <sodipodi:guide
+ position="3883.8708,741.49345"
+ orientation="0,1"
+ id="guide4697"
+ inkscape:locked="false" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata5">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="Layer 2"
+ style="display:inline"
+ transform="translate(-46.896674,-179.42294)">
+ <g
+ id="g6697"
+ transform="matrix(1,0,0,0.95416667,0,10.813822)">
+ <image
+ width="29.903757"
+ height="10.697155"
+ preserveAspectRatio="none"
+ style="image-rendering:optimizeQuality"
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZ0AAAAlCAYAAACZMTQOAAAABHNCSVQICAgIfAhkiAAAANhJREFU eJzt1bFtgwAAAMEYUSQFFBEzMg5LsEVGYBZE78qZIo8U303w3T+WZXmd5/kBAH9pXdefwXAAKFzX 9TXcHQHA+zAdADKmA0DGdADImA4AGdMBIGM6AGRMB4CM6QCQMR0AMqYDQMZ0AMiYDgAZ0wEgYzoA ZEwHgIzpAJAxHQAypgNAxnQAyJgOABnTASBjOgBkTAeAjOkAkDEdADKmA0DGdADImA4AGdMBIGM6 AGRMB4CM6QCQGeZ5vrsBgDcwTdPzsW3b93Ecn3fHAPB/jeP42vf9/AWoNBUXx/LG4QAAAABJRU5E rkJggg== "
+ id="image4638"
+ x="60.220459"
+ y="185.83905" />
+ <path
+ sodipodi:nodetypes="cccccccccccccccccccc"
+ inkscape:connector-curvature="0"
+ id="path4569"
+ d="m 53.961054,182.24537 c 0,-1.18467 -0.02564,-3.06908 3.689545,-3.06908 7.699536,0 24.843888,-3.1e-4 35.936576,0 2.673147,0 2.673147,3.3698 2.673147,3.3698 0,0 0.08347,9.53394 0.08488,21.8929 l 0.0021,20.50537 -4.089856,4.26283 -4.089848,4.26285 -31.646457,-0.0175 c -2.559641,0 -2.559641,-3.32743 -2.559641,-3.32743 V 182.2453 m 31.760891,40.60948 3.825229,-0.015 4.234526,0.015 v -40.03579 l -36.85476,-0.002 v 47.70131 c 10.424891,-0.0325 16.911425,-0.0246 28.795002,-0.0246 v -7.63872 z"
+ style="fill:#000000;stroke-width:0.42511523" />
+ <image
+ width="29.903757"
+ height="2.6499548"
+ preserveAspectRatio="none"
+ style="image-rendering:optimizeQuality"
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZ0AAAAlCAYAAACZMTQOAAAABHNCSVQICAgIfAhkiAAAANhJREFU eJzt1bFtgwAAAMEYUSQFFBEzMg5LsEVGYBZE78qZIo8U303w3T+WZXmd5/kBAH9pXdefwXAAKFzX 9TXcHQHA+zAdADKmA0DGdADImA4AGdMBIGM6AGRMB4CM6QCQMR0AMqYDQMZ0AMiYDgAZ0wEgYzoA ZEwHgIzpAJAxHQAypgNAxnQAyJgOABnTASBjOgBkTAeAjOkAkDEdADKmA0DGdADImA4AGdMBIGM6 AGRMB4CM6QCQGeZ5vrsBgDcwTdPzsW3b93Ecn3fHAPB/jeP42vf9/AWoNBUXx/LG4QAAAABJRU5E rkJggg== "
+ id="image4649"
+ x="60.220459"
+ y="203.46257" />
+ <image
+ width="29.903757"
+ height="2.6499548"
+ preserveAspectRatio="none"
+ style="image-rendering:optimizeQuality"
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZ0AAAAlCAYAAACZMTQOAAAABHNCSVQICAgIfAhkiAAAANhJREFU eJzt1bFtgwAAAMEYUSQFFBEzMg5LsEVGYBZE78qZIo8U303w3T+WZXmd5/kBAH9pXdefwXAAKFzX 9TXcHQHA+zAdADKmA0DGdADImA4AGdMBIGM6AGRMB4CM6QCQMR0AMqYDQMZ0AMiYDgAZ0wEgYzoA ZEwHgIzpAJAxHQAypgNAxnQAyJgOABnTASBjOgBkTAeAjOkAkDEdADKmA0DGdADImA4AGdMBIGM6 AGRMB4CM6QCQGeZ5vrsBgDcwTdPzsW3b93Ecn3fHAPB/jeP42vf9/AWoNBUXx/LG4QAAAABJRU5E rkJggg== "
+ id="image4671"
+ x="60.220459"
+ y="211.76212" />
+ <image
+ width="23.725101"
+ height="2.6499548"
+ preserveAspectRatio="none"
+ style="image-rendering:optimizeQuality"
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZ0AAAAlCAYAAACZMTQOAAAABHNCSVQICAgIfAhkiAAAANhJREFU eJzt1bFtgwAAAMEYUSQFFBEzMg5LsEVGYBZE78qZIo8U303w3T+WZXmd5/kBAH9pXdefwXAAKFzX 9TXcHQHA+zAdADKmA0DGdADImA4AGdMBIGM6AGRMB4CM6QCQMR0AMqYDQMZ0AMiYDgAZ0wEgYzoA ZEwHgIzpAJAxHQAypgNAxnQAyJgOABnTASBjOgBkTAeAjOkAkDEdADKmA0DGdADImA4AGdMBIGM6 AGRMB4CM6QCQGeZ5vrsBgDcwTdPzsW3b93Ecn3fHAPB/jeP42vf9/AWoNBUXx/LG4QAAAABJRU5E rkJggg== "
+ id="image4682"
+ x="60.220459"
+ y="220.06163" />
+ <rect
+ style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.87403107;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect6711"
+ width="2.3547916"
+ height="4.9358077"
+ x="63.249397"
+ y="188.49934" />
+ <rect
+ y="188.49934"
+ x="68.613091"
+ height="4.9358077"
+ width="2.3547916"
+ id="rect6766"
+ style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.87403107;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <rect
+ style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.87403107;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect6768"
+ width="2.3547916"
+ height="4.9358077"
+ x="74.238426"
+ y="188.49934" />
+ <rect
+ y="188.49934"
+ x="79.340477"
+ height="4.9358077"
+ width="2.3547916"
+ id="rect6772"
+ style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.87403107;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <rect
+ style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.87403107;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect6774"
+ width="2.3547916"
+ height="4.9358077"
+ x="84.965813"
+ y="188.49934" />
+ </g>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer3"
+ inkscape:label="Layer 1"
+ transform="translate(0,-4.4914085e-6)"
+ style="display:none">
+ <rect
+ style="opacity:1;fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.48279953;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect6778"
+ width="50.889664"
+ height="12.689711"
+ x="3.0089004"
+ y="7.8493094" />
+ </g>
+</svg>
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..bdf46e6f46c92af3d5728711498456b1d7e8a2d4
GIT binary patch
literal 6834
zc$@*U8cpSiP)<h;3K|Lk000e1NJLTq003M7003eL1^@s6-Ji6D00006VoOIv00000
z008+zyMF)x010qNS#tmY79{`x79{~mQY7#I000McNliru;Rp&5EC=+k{%8OI8b3)y
zK~#9!?VV|m9oKc=fA@C3e)}w#eFht`k^m`gBB+RzsD&aeQX*r@vf_$k<wK-cskl`6
z7Q2#4<wGh_RH=%U$YrZsjwL&e%9SdK6iJa3DN><C2?7C<Al4ZyBruqLdGq$y-S?h+
zcyIRi27|!>7|6U;T`0`->v{d}bN}a@|2^lnI)DB=T5AB5Qp}E(ezw~GH~QJ-##%me
zvy9)|JNp3e55@<8-+<w+<wKRQLOHDbv-8>azd2c%?mC@0$6#)lye(1)O9-;yiRzo$
z3>>9N=s4S&`e=!^9Nyo$>-Tz6-2<J8cGA%l3bpbDL?8$t5&@-F54aVJh)@hlf0XkJ
zJEr{%{WE9mcxi%TQ>QsQ^FDb~Bp(#93jn{_=bft@Qcj#Hf1I}1CUG&}DVy?$5&OSF
zF)u|OkGLB{E4?!4wV_rUr4;<)zs?2uC!fFY^8H80kEf4j`WY#np&}Km4zYw-ZLnB$
z?J}a)3b@>Oqy?mQ0}74CMT^E2mraqL_d4TS4nMNx!QmZE+kX9-EuZ+CbToyl-K(dR
zit$@UOXGj>%i%Zg8Y&L=9G*Csyyy=zVaF&q87vkeAWj4#4p9|FX$N%`p`C05zyUD^
zBL-uAj0rGe2z<?i9_}rMg|`1U7UWN5JGYLM#_xNe`@qGnM90&;>7IVQ0?n)lcxXfN
zLH--3Ci@Tk==~pW@6VrSq;eJ;2~mzgCpC^%=tu<Zr63X^={6H_Tq4T73cxo6+*C|J
zU<1lw9_!B_#t?b|zR7tNoArJ<HJps8)F<DaJddNC)B9R?f2uvc>9d~WHKdq^lwy@|
zWcZywKb9T%{I^bjx3?(o6RHYfBtRUC<3{Li-9>Y36Z@KW(;n*}=Eg}mNfa72`v=$J
zGdDouBM68kl#rq+F<u%cA7(k4ImJX_keT8z$_K2%F&-(kmF{RC|Ni!WmD$&}_t^HP
ztsh&pg;$K<lMf3|9nbdfJ((Too$`kuQiO<wh(|i=5sNj`+q9Kdua)jZH%*ad;*mHp
z6+>wSv(xG(1-$C(ELIG08^_U#e3)d~6p1<t&w+`;G=zrG_sIlfNX7E@^of>uB(b?Q
z+Coz_y)25U3;0NJ?Ee1D*{}WS$?tBR3Wvd)Ld67eBalwBz2$c9Y~9b^)DEJKht`_V
z8kANTf||3Gn;vi_3WXx-c=Sbk0oc>Dli~6h7fVAtGyD>T@)YE!Q7X%f$??q-f5DfE
zBikd&J#=69UH3(uXk)+yA@3LZ4vrnU<Jk)@CbQ~7nrNX4b}B_8*1{*dKSo!)i;n0f
zD$2)~0I?PkP@+-D!YF7>0ixbOap9~3RCe|uXyRUsTih)?p&w?rG{S46zeMuWs31g`
z<nYuRRQ%$H`%>M<JL4VulU||$;3CXavb%?hLyw%vpKA$K0U{Rb3Q;djJhqAML@%vg
z6A|qZN&q5QRfAQWIuYKLLvA->0xZ=ySBt_`4lyT6Pof(GBx22!DtW|)DA#AqjMAS!
z!$f(Cgq!#eNiXpq>jGXd#q<joU)k~Nsb6g#HYZzb!s0~1rd&SKaTh(QJ`xd+G9?PO
zh@(~2f~B!J>$L>qR<U7+QXrrNBHE!lzKPHGe3Do7j~OeDBH4gkWt{V2f#)y2v~PdQ
zZNK|FyPjAMxNm%NmHY$8GbjJ?NM*zm*MbWY_2@|Sk&HIsIWZ~}F}7;e+PF2}XjcGS
zug#h^>JU}U90h#kAj*=A#A%QB0GjdgAYu(cQ0Cp4<7n+}DTifdL&Z73OF@a5O1A4*
z?$jM4X1v#WfCn)zN^f!-$w(aSSaQ~{g-ldqO<8|F-pF{*JJjkI)o47&r9Iw-(hifk
zA&fN`YdDb`AnvA;dB5<T=9v4{MSu@v&VS{6@%*C~O9K=mdGLUk#*4;jk8PqTW&F@b
zX1&Mk^jb{5>k@HXx5P)um09+JplkkC6<QpUkrXJ4iaS7nSYbSSp7#qeeR$%X-#gHC
z&sSTcE%Sh9f|<{ZS0)|_wI9VX-~bU9<$5S31QK9GEm8tkCyQ)MN)f=;9C{-qLFOP;
z=sDwBihx3iLX@p0j*%$%7My@kRVY$mxHPIN)}Q0>G?44hp6xi6J(adz2$9-vybi)h
z6CpljF<84KjMq0O;1*Mb&96GZs$-Wr=T*Z(tsI<)2CWDQ=99VwUpFF}K!u3c2Eil*
z0V<}*syv4#-by~ub3fhjPF$cB$W8f^+sDe|)oG>yt#Gu$(Uzi=s$Wr721c(FKZsmQ
zs4Y<&C2dmN5$+{M6h}FAEVoJ{N@*O&;e<NPG#N5d0tC(C5ZD(*AO=?}tk#GYL_;7x
zL!}{nGjHK8z(ggpeY8A@iAa^3wZc^z5DK=0!7f8$DKO4P2D}zc74hjd?X;K_9}N!B
zqML|BT%-=*3I!U((G1B5Q<7oE=5SEBOYYV|6N)9G1*3(qX0&BfX0SLy#a0%Fg+(cm
zrC3d=bk)jzd@PDdo2@M>*d<>`mRPv6VIvL;gks54;zQidqmfVX(2fU5c}bMpIPRYc
zr#V<S%#n#xobt~T7ll^KV=slmh)^Ivl&P_V5Q-ra<_R&20T&DwQ?u?_iwLlys*4Pm
z<rcln6rFY9W4Q(<ZfiFj594%r?QDtlk%}dWN8$~pCRC(CNy_wxXPGh6luQ;!$7|u>
z^6=M+Al9!w-UZLlC^H9xyJ+TXJHJY!1en2y!?X!%0GlPiC@#n1a>D`F)k94Y6^D<6
zpXMV?ck_?h4iNKV4IRHAjQf+kTRh3P$N!44@B-FW$V%8C2G$@JWCpO+gj6fUE(qEe
zr}i=rv=&exHUwk9nXa9(<c~;X73f(u1c`@+q-y5L_+#9eyq#n^S(B0L4_|zKka>oq
z#djI4oFTvm4B)S*E{e5)MU<*yxd7}s@@#_}wWjWGg<y3;{stIPiPNlF*cIDGN3;#c
zbs9e#2`Shj{iOlUmCxauVwEBkuw<5Bshupba9ho&Z0*ycz{=e0QFW*5YTVTtR$JD2
zJYXMUpSzEbc7BBBL^CT@Hw;!TalCYbA5Q&WM*WcmYT+t{Hw#LnO1YvIVc^yWNYyud
zS%X7di(0tecw6DF#2wrdyO(4<xuSLZHlV*ez$@8T$%Zq8HdroUtWDc(1o)CUax*AN
zmyt`N#^a8}e(p`)h39z<x!WQXZIM%@)4V$KDkW2@I`At1T*=B#-(76kH#xdXhY`9}
z4^MheaevDJcBXf%D5e!{k!Po$=e5jhoX8y`&;jW6(B|zbNLbWOkH1Td7QKnvV*6<F
znu)lP6{C`@$?<OKT?T_ed?{ZYiAa4fI%nakM)u38M=gjL<;wY4*7|L)ezclac6d8^
zs^dx8lkHW>xnV{*#reuP{%qpg<oztDD^qV*{O${IT0kjgjelX|4G|$KF@7iU6nCcX
zq+?SDp1X3+@KWX#-YL98Sgur;(A0V^^EHf8?DdfBuKCd75GTeSZ#O%l+lhK!L-Mp(
zeDOI~KFdI<A7jG#n0pNmS}mK^rcsnq&5+bw5u+>v-!5g8rCDU186r1;Qo8A47d!b(
z`@?KbY;LHP0OU-LxARANZt_PQExbdZ%=*X+HH_8e<M0h8^Id5eXcV_Zw((HxA=*-H
z#G~<s=LQlm>W}jL^z)4Rqxd$wx>D(e)eE*!rg`&oxE6<9k=;C$c!=hBGhW1Nn9UYV
ziA(+vFJzu&DwwR!?rY;!8i`kS`8jrh7J0c$vrg2a)=+x9HX2eSi1BIXVIFRJnESi#
zC+5Z)a&aI5FHFC{k^B)(X8Q@WU%k((L0r^46)mFWMsc{*w3M|dd>bI6bcwJhx`&=<
z4+$^PFw&@sQw5Hdk26p?Ln!{W%)S;Sl`g*_L#&W;QasiA6uX*s;lvwz%ACz{!N15~
zPy8i=<>A_cxjuC2F1Bu|6=l|@hAgIbwE~66YGe3okSr-He8T+{`=Yn8yKOfuiIx@5
zKbk+vOPQC*l?teEcJ`}vnPnn1#c&QkwTL6v#ljtNFk<TR<uZ0s>rW+!W{0zbz207$
zqD>7`ctNO0h2dbB(}mLn##F&j*SeOmE~OZ*RJ#@}#H>eK*h(eH;}a|cbauJbVli6C
zdwx}|Q|mQF9a>29Xxk$kNZn5)9%*=OUwn>a5A(|O%RD{x48{i6iSSm6!)Jx_dNfcc
zd}wuB`0eCVM2&|Qy)0pHwMLY}T1!z1yzalj5F<>;#L8B?%5bJdwem^tA-1Qsvnkcu
zu(u|ZkZhRcm6?}0T{w*~^E&ssDH?UOCS_$2Engj_2(-FwJlgVEw#hb9I#s2Xg%++g
zN+~1|ro(B<lRk&+Ato6^l)kE)b41gwJ9#AjTWm>gA)QP&JiTN~Ooo%ZoOy|X^0|3{
z)b%ZmcCUszwGk$QG5)&pSNz-FKVe65$FgeKIsI9nBkbTSv42T)(&N2cKRKteHqdKe
z?^U;QZ|oi(>wSz(iA^h}@ULcG<yheuZ_gZ~V2ibzxc+tMSM4;*5;7W&l9L=|RVM8$
zZX{YH_-knQ+S%l_(W2TY$|wf2CeTG`+EqL4PCIEYT`O0s?<kEJMuQQC{7aN=1xt8C
zz;3|dOES#`Li$3kvr>gMAIxxY@=czJ{)mf}i+rK;3wWx2I_)|xTf4UM7>|?o)BKm>
ze`QLh(Mz25<uXkX@bLIj^FLu<(>_wosfHRz#a0-pjPSjQzva!`LHt^hqpm^REwa$x
zRS&zG`pZ>m7B%!O)dt$17hdPZ%!^zqU!q_NjX&pV`jVUZ@TLRo*ZbMV)+_c&9dz;`
z=XUnD>}Pv=`-;Up)8RC)&Ai5Vag1`szfp#L1(dSD!`da$%Um?0;7ImO1}m5NgU&x7
zq9Y`oWCQYtu6P%T)&zHr--%~EF3N?)Hok(1i-lg@!`;r^>~7jkN4%rqxuJwi1{1t8
z{W8;)44C=8uWKgvi>#Pz=mkObf<mDMp^Tb8w321VY?f(4nDMiGYw*AG^O@J@szvKQ
z<)}+ja}!_g{O5eJ^$RowF(Ts3T~jIQ9<x*G?)csO(~jS#HQkCEa~miahIevr^Lpmz
z{BYuFrp)-9Q*Ctpt34L*?Gh?hS3oJUkdM@|W2pjnp)|;a(glVq!-R8nc+0I^YfWde
zlg)`O?1|h;j2L38*OXd{y!3F<Y}Q-Y9NSD!qNlohgQ`y@fmG{D&X&)T5A%d#=Vof7
z>tF4$8d+l9x@nZ72$ave`D45`^)p_We1YO@_*L(GrRZz#<DQOt`C9txY}Q*=sv-je
z3ERXMqhH_?&7WXr$4*?QzI6vSU?#}&cVmCcvy;!v*EVf{ZbWxkMp-vXssHrUvW!#^
zCA?X9i!n3C9Zh$zGroh~XkP;n23is=d}_-ldGq|Q*<@NdWDnsJE)I!A(L$QdPA{MD
ze1bjcJ=M0D24kQ!z@gkBj%80W=8s&x&V8k84a|#HOEFsaelevq88gY5@;NS*E-~tl
ztynAQX^+lCC)>U4+~VAVZm{h~t7@go>0nD@3(e8SwSl38j07W`DxPAxlA#oq*+^sk
z^>9@!0u914e>wae?rXW9+mg58X|Hb2rg9Ws%HxU7C)pR@&(8*b!nh78>u~N`7~!+&
z$GInQH=Elw6N|(ef<06j;>GC~__N{vK|%ZtK_d+;ybhgQ#V{Do<<E0`<~WD5hslTe
z70>TTb+Du57CsgG3_IPOa~VUF2wT)<?n>RshnnxKO0Ny(W!aYbS>|W-=LabGl^d46
zFT-y^pObr8RZ%TmfKV%EgbRLefeYn992Magnb+xy_K|dxI1L1k=2SDT=kiF?XDJlQ
zoc7NV>VPN-b~-!Q-?X1wn{LI?^*?XKkTW@6o%$(f@@F7W^LHoehJdZ={9U*1f`K$j
ze3tr1yRtl~6&J%nzIo}t(Hq~)0Xe{~#IE}7;10y2aUR|LTNKBN%=j68?!Q5w*TuhR
z`zkwIc9Ke_8dAiG!g2bG{X94IJkx##oEud6D-BA$SLHP2>iImd6{h`Z&KA!yP#PeR
zprI4hN|TDF=!^BSH@cT?Y8zXf?c9>Qg?J><u*}$Cc(42({iS|#L6*S!8-lN8rDD!&
z`ow+3)1yD&Wd1k@(g$dbwBpu_MifMn5$+D}q1|@UX}ai4baHR^y?9Ztu5}v>MN{PI
zi63xq=GO#aFn6tuIIhd8y*R?T!h7Vy63<OM%YDrU*q_|r@cd1wO(eVoZB3i-Tn{ha
zxQKo#nBr&Cukm)~UCtFQ5Gun)9@if5S^|?d1$1Sc{=xuTVq4jh*h56s&t5#&!*e_m
zPPMkA5!j)G<boV03MZKKCn$!68(a7xv%Niyr&xVGd@kdZMO>~f;}BL-;6w>I$@8tD
zKcz&89<PVp$=&rquchIQgsEy2WEt^CI8r#wHwV8#$*(MmsBRSC^B79q@OrV+v}*AO
z)_GX5l>7=Ob0_)9)KAEp{IWIBRmb^aIGFtvN3usKR7x1J8>_zEE^wSiR=J3L87<{X
zyV{f}#0UfV0X*&USjS^{%4@8huK;$@7I|a(7o5l)M*_g9wJh9F3s;vdb9NVMxN1(q
zlm_CJsz=XP6h<no7%E+2#?SJ@<{z>zwU5U;9>1dd8Y~ZTvT%Z@N1ox5e~GK#U3i_7
z9;+77%0is3|9*7}LJ26C0w)S5=r8qCFoo)-!K<37mhoVm{z5;~VTQ8tH*5%a87<{{
z&OA*(!1qSK$M;9R&zaI0N~W|50}{%n%%SW-{$}KR6vF)SMz5O<a3zgJtl1^FMLqj#
zIcBdHY6gpgeEa?X$@$WG{L5sBB~#+Xi5L0B<S#inGeAXx>LvnL^Ig{QP_ymwjVO0T
z3%?GAqr_4SioBb9moa~goXIsz`)3{HslrJHOM?`G4Q|~HzS|A04;Q7$n=Efly~T5*
z&vB`Ai7)kjsji|p9*lFYe2#Aq{VC(+>5Z|A{<>$H%fdmaBP}kp*tLNWisg-&H<&OJ
zJlOgm-SO^aq2k-ww>Vxn&U87CZ^Aj5d&61z5?Z-Dr7S8WzdpHyDB)1{O@2D{Dwisk
z$k!<(dOP<HzncCvVI>43h*DMl-f&d17(->HKHxE(An7#SkQEZG1qp?0d4}%}ewTxp
zU(Pwuyvg%c_6R?n_$jYVzDB5St}xKi9!*a3oI0w35FoDOB%Q?Sgpq6FKovn~IF&!e
z#nK?TFo(rbwq@QcooA>r#DqVNoh`f(+2Brdma{cUcfQ!vh3Y;^TNB&J`Z>;J->+`U
zVp-g)QZJLg+>+ZBnYmD*%)!Y+w0j+-bdt|^Kf!y&^L*>QZ*i$`v1%Ib2JcFr?Y^`M
zu|mR4u)k@)Bx*j%rP^ykU%F=~FWFuv1EHDcqU$YBSOQKJPw>p7!gV5yRE9ZII7iu*
zYAO5%k4|JkT5lntlI(5WQ%JapIl%J(y~)kPWvTQ!L_mVYjh!pkC$2NMX!^@%c;CK<
zr=#S8JcFf6bDLLgXv<#Gnxjx8-2{7D_6{f9_#EIxfZa{Izn^kb@xEBkzF|8`-sZt5
zloIB*30Id<3uPS)q$+1tN&(LfJy+Yj@+Q~{s<t7UGSz5VSw&~0oo&gj+}rWtucy59
zk|@QE{UGHfk9J18s_){l+&HqV?S_^&1&XH3%`|5FG_`2DVqJ8`x=BWoKVIlG0Z2p=
zXWLZ!%*VSvK4!<IWh^)9NvIHAeQI;b-}RuDn{A_<T6Gw<`mZwJ*a#1{e~jB(Za<Os
z()BaVxEufDPxn5Y&6vy=&lS&ZpO8t!7-m~yM9HG{MYW2}4c1YH*tjMwQtfEA)gU`X
zlI}=1pXqyKdRuba-!;rM9py}SC%RwnPIS`~>#DX3SWu>RScMW>U8wkA%v*+4$gFi+
zMaoIj6Yr)k(f3A2tm8j51YDs=d+8rN)cJ}3^OaqH_(F?LL#V55_y&}<h|$%Cs(<L9
z*TO!GLYZ3kl&vlyKG61n|3`biKG2uy`&`mXuB@df6m7A#Ke?^tw)a2R_4tVgTR(b2
z8RaVr5>`Q1vrqINBIr^B*C?=6u*FoVB(CB-(fv6-*732SJ<WUmKB}Xudly@Cq?xpr
z{=>(*9(&+u{^*kjvxmNBt?{g?K0zgdI0`Wulq{g`TJWgWVZW#il%Cg(x1|cQpiov3
z#iPS%KlR()PZjQH{m@UgrndgOdY5b{&$`Ox!yWhiVn^%FyLw~27rsCEzgx~0&h?J?
zlZaN}SY}H~loC+YZp&&(w^|==+(@7==v9^3kU3Puh|;Co^S^(~m$@~4>-fjJKmPH=
zLQQJ5fCEIGC@r)+yCbzz-=4nn%W0>%_eAL=8JnSuPdTX8U@J$@eJNGjC_=8n$~Ow!
zN|rqTTA7Dd4xVyp^V(?9&Fqcue)G1}tpm3tb{tDO$%b9ga0Tx~&IkEF&W5>tPhb4O
zhmT|q-}-j$EyjZ>LPC@aQhT$<y2R2OX*Fk-FK1f;Luhi-bVl2Fyz_~}yOX>5Y~N%5
zrpar1eKqeyo(DV#YA^nY)CJsFgH@Kv^3>l?hU5FkD&w5Wo!mEF$#AN0iuVieQM3he
zHeZV&ufoDN2;j;p92Fs{(`-v@r`2s^f75NUHL-bWb8^efmgLrFlTPwKw|Q+Nj&?5p
z%JoH*av9!qv_nKk=uP(g?xwIcvN^itOK}~0bi6Wtn?m)t*6GdJ99hXATxA8T-Z<7B
zQ4!L*nO%w9ba)-y-EwzfYkccB`jUO$Oh?mK{I0oGQ_8}h&91DAs=oauv>_IIKq+-e
zu#3v5wAL?QG2U9A+endM^)>6d`nEvrqEz)KY7IKangCzz{~P^m6@dT7hVj3-clH6`
gAB+zGzX9X_11S?Li!{K2tpET307*qoM6N<$f&qAHng9R*
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-linux/ok-sign.svg
@@ -0,0 +1,147 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="14.996866"
+ height="17.165913"
+ version="1.1"
+ id="svg38326"
+ sodipodi:docname="ok-sign.svg"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06"
+ viewBox="0 0 34.992688 42.914782">
+ <metadata
+ id="metadata38332">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title>Highlight</dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs38330">
+ <linearGradient
+ osb:paint="solid"
+ id="linearGradient4972">
+ <stop
+ id="stop4974"
+ offset="0"
+ style="stop-color:#d5d211;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ osb:paint="solid"
+ id="linearGradient4964">
+ <stop
+ id="stop4966"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2780">
+ <stop
+ offset="0"
+ style="stop-color:#ffffff"
+ id="stop2782" />
+ <stop
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0"
+ id="stop2784" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2834">
+ <stop
+ offset="0"
+ style="stop-color:#e59a00"
+ id="stop2836" />
+ <stop
+ offset="1"
+ style="stop-color:#faff7d;stop-opacity:.18367"
+ id="stop2838" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3057">
+ <stop
+ offset="0"
+ style="stop-color:#282828"
+ id="stop3059" />
+ <stop
+ offset="1"
+ style="stop-color:#282828;stop-opacity:0"
+ id="stop3061" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2713">
+ <stop
+ offset="0"
+ style="stop-color:#000000"
+ id="stop2715" />
+ <stop
+ offset="1"
+ style="stop-color:#ffffff"
+ id="stop2717" />
+ </linearGradient>
+ </defs>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1199"
+ inkscape:window-height="731"
+ id="namedview38328"
+ showgrid="false"
+ fit-margin-top="0.2"
+ fit-margin-left="0.2"
+ fit-margin-right="0.2"
+ fit-margin-bottom="0.2"
+ inkscape:zoom="12.183333"
+ inkscape:cx="11.164775"
+ inkscape:cy="-1.8436479"
+ inkscape:window-x="46"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="layer6"
+ viewbox-width="18"
+ viewbox-height="22" />
+ <title
+ id="title38274">Highlight</title>
+ <g
+ inkscape:groupmode="layer"
+ id="layer5"
+ inkscape:label="Layer 1"
+ style="display:inline"
+ transform="translate(-9.0161178,10.157503)"
+ sodipodi:insensitive="true" />
+ <g
+ inkscape:groupmode="layer"
+ id="layer6"
+ inkscape:label="Layer 2"
+ style="display:inline"
+ transform="translate(-9.0161178,10.157503)">
+ <g
+ id="layer3"
+ inkscape:label="Layer 4"
+ transform="matrix(1.9578174,0,0,1.8768146,1.7731116,3.9800016)"
+ style="stroke-width:2.96904826;stroke-miterlimit:4;stroke-dasharray:none">
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#008000;stroke-width:2.96904826;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 5.1029077,6.1459338 c 0,0 4.0105782,2.9252466 5.7695983,5.6644272 4.278402,-11.00488643 9.869697,-17.5760024 9.869697,-17.5760024"
+ id="path4586"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccc" />
+ </g>
+ </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-linux/password-error.svg
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="25.169001mm"
+ height="25.169033mm"
+ viewBox="0 0 25.169001 25.169033"
+ version="1.1"
+ id="svg8"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06"
+ sodipodi:docname="password-error.svg">
+ <defs
+ id="defs2" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="2.1594276"
+ inkscape:cx="139.34847"
+ inkscape:cy="68.416928"
+ inkscape:document-units="mm"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:window-width="1399"
+ inkscape:window-height="855"
+ inkscape:window-x="41"
+ inkscape:window-y="1"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata5">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-8.7120523,-4.2052231)">
+ <path
+ style="opacity:1;fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.83094561;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:14.39999962;stroke-dasharray:none;stroke-opacity:1"
+ id="path815"
+ sodipodi:type="arc"
+ sodipodi:cx="21.296553"
+ sodipodi:cy="16.78974"
+ sodipodi:rx="12.5845"
+ sodipodi:ry="12.584517"
+ sodipodi:start="0"
+ sodipodi:end="6.2656622"
+ sodipodi:open="true"
+ d="M 33.881053,16.78974 A 12.5845,12.584517 0 0 1 21.351682,29.374135 12.5845,12.584517 0 0 1 8.7125354,16.899998 12.5845,12.584517 0 0 1 21.131168,4.2063099 12.5845,12.584517 0 0 1 33.879121,16.569231" />
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.08675575;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 24.849867,9.3258801 3.897186,3.9041669 -3.775033,3.781809 3.51511,3.52141 -3.61439,3.620874 -3.515111,-3.52142 -3.614391,3.620879 -3.897185,-3.904175 3.614392,-3.62087 -3.591526,-3.597964 3.614398,-3.6208701 3.591517,3.5979611 z"
+ id="path817"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccccccccccccc" />
+ </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-linux/sign-active-18.svg
@@ -0,0 +1,340 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ version="1.1"
+ id="svg38326"
+ sodipodi:docname="sign-active.svg"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06">
+ <metadata
+ id="metadata38332">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title>Highlight</dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs38330">
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top"
+ id="linearGradient38877"
+ x1="0"
+ y1="0"
+ x2="7.7620873"
+ y2="0"
+ gradientTransform="scale(0.64415662,1.5524175)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top"
+ id="linearGradient38879"
+ x1="-1.9364917"
+ y1="-11.61895"
+ x2="1.9364917"
+ y2="-11.61895"
+ gradientTransform="scale(1.2909944,0.77459667)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top_center"
+ id="linearGradient38881"
+ x1="-1.4142137"
+ y1="-12.020815"
+ x2="1.4142137"
+ y2="-12.020815"
+ gradientTransform="scale(1.4142136,0.70710678)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top"
+ id="linearGradient38883"
+ x1="-2.236068"
+ y1="5.0311527"
+ x2="2.236068"
+ y2="5.0311527"
+ gradientTransform="scale(1.118034,0.89442719)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top_center"
+ id="linearGradient38885"
+ x1="-1.8708287"
+ y1="4.2761798"
+ x2="1.8708287"
+ y2="4.2761798"
+ gradientTransform="scale(1.069045,0.93541435)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top"
+ id="linearGradient38887"
+ x1="-1.0473186"
+ y1="10.741717"
+ x2="1.0473186"
+ y2="10.741717"
+ gradientTransform="scale(2.3870482,0.41892744)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ osb:paint="solid"
+ id="linearGradient4972">
+ <stop
+ id="stop4974"
+ offset="0"
+ style="stop-color:#d5d211;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ osb:paint="solid"
+ id="linearGradient4964">
+ <stop
+ id="stop4966"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2780">
+ <stop
+ offset="0"
+ style="stop-color:#ffffff"
+ id="stop2782" />
+ <stop
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0"
+ id="stop2784" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2834">
+ <stop
+ offset="0"
+ style="stop-color:#e59a00"
+ id="stop2836" />
+ <stop
+ offset="1"
+ style="stop-color:#faff7d;stop-opacity:.18367"
+ id="stop2838" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3057">
+ <stop
+ offset="0"
+ style="stop-color:#282828"
+ id="stop3059" />
+ <stop
+ offset="1"
+ style="stop-color:#282828;stop-opacity:0"
+ id="stop3061" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2713">
+ <stop
+ offset="0"
+ style="stop-color:#000000"
+ id="stop2715" />
+ <stop
+ offset="1"
+ style="stop-color:#ffffff"
+ id="stop2717" />
+ </linearGradient>
+ </defs>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1199"
+ inkscape:window-height="731"
+ id="namedview38328"
+ showgrid="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:zoom="14.75"
+ inkscape:cx="6.2137977"
+ inkscape:cy="8.1121029"
+ inkscape:window-x="46"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="layer6" />
+ <title
+ id="title38274">Highlight</title>
+ <g
+ inkscape:groupmode="layer"
+ id="layer5"
+ inkscape:label="Layer 1"
+ style="display:inline"
+ sodipodi:insensitive="true">
+ <g
+ id="add"
+ transform="matrix(1.3457681,0,0,1.3457681,8.0886149,9.9113851)">
+ <defs
+ id="defs38291">
+ <linearGradient
+ id="top"
+ x1="0"
+ y1="0"
+ x2="1"
+ y2="0">
+ <stop
+ offset="0%"
+ style="stop-color:#e4ba2f;"
+ id="stop38276" />
+ <stop
+ offset="100%"
+ style="stop-color:#c36a00;"
+ id="stop38278" />
+ </linearGradient>
+ <linearGradient
+ id="top_center"
+ x1="0"
+ y1="0"
+ x2="1"
+ y2="0">
+ <stop
+ offset="0%"
+ style="stop-color:#f7e1c9;"
+ id="stop38281" />
+ <stop
+ offset="100%"
+ style="stop-color:#f4cfa5;"
+ id="stop38283" />
+ </linearGradient>
+ <linearGradient
+ id="hold"
+ x1="-3.3561783"
+ y1="-3.8734536"
+ x2="3.3561783"
+ y2="-3.8734536"
+ gradientTransform="scale(0.59591592,1.6780891)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0%"
+ style="stop-color:#f8edc3;"
+ id="stop38286" />
+ <stop
+ offset="100%"
+ style="stop-color:#f4d644;"
+ id="stop38288" />
+ </linearGradient>
+ </defs>
+ <g
+ transform="rotate(45)"
+ id="g38295">
+ <rect
+ width="5"
+ height="12.05"
+ transform="translate(-2.5,-7.5)"
+ style="fill:url(#linearGradient38877)"
+ id="rect38293"
+ x="0"
+ y="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38303">
+ <ellipse
+ cx="0"
+ cy="-7.5"
+ rx="2.5"
+ ry="1.5"
+ style="fill:url(#linearGradient38879)"
+ id="ellipse38297" />
+ <ellipse
+ cx="0"
+ cy="-7.5"
+ rx="2"
+ ry="1"
+ style="fill:url(#linearGradient38881)"
+ id="ellipse38299" />
+ <ellipse
+ cx="0"
+ cy="-7.5"
+ rx="0.75"
+ ry="0.34999999"
+ style="fill:#3d2c0c"
+ id="ellipse38301" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38307">
+ <path
+ d="m -2.5,4.5 2.5,4 2.5,-4 z"
+ style="fill:url(#linearGradient38883)"
+ id="path38305"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38311">
+ <path
+ d="M -2,4 0,7.5 2,4 Z"
+ style="fill:url(#linearGradient38885)"
+ id="path38309"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38315">
+ <path
+ d="m -2.5,4.5 a 4,4 0 0 0 5,0 z"
+ style="fill:url(#linearGradient38887)"
+ id="path38313"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38319">
+ <path
+ d="M -2,4 A 3,3 0 0 0 2,4 V -6.5 a 3.8,3.8 0 0 1 -4,0 z"
+ style="fill:url(#hold)"
+ id="path38317"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38323">
+ <path
+ d="M -1,7 0,8.5 1,7 a 2,2 0 0 1 -2,0 z"
+ style="fill:#3d2c0c"
+ id="path38321"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer6"
+ inkscape:label="Layer 2"
+ style="display:inline"
+ sodipodi:insensitive="true">
+ <g
+ id="layer3"
+ inkscape:label="Layer 4"
+ transform="matrix(1.0666667,0,0,1.0666667,-0.27745623,0.00654138)">
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#008000;stroke-width:1.74388254;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 11.90849,12.21419 c 0,0 1.39836,1.035106 2.568841,2.4924 0.454732,-3.871985 1.725652,-6.1303502 1.725652,-6.1303502"
+ id="path4586"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccc" />
+ </g>
+ </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-linux/sign-disabled-18.svg
@@ -0,0 +1,368 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ version="1.1"
+ id="svg38326"
+ sodipodi:docname="sign-disabled.svg"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06">
+ <metadata
+ id="metadata38332">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title>Highlight</dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs38330">
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top"
+ id="linearGradient38877"
+ x1="0"
+ y1="0"
+ x2="7.7620873"
+ y2="0"
+ gradientTransform="scale(0.64415662,1.5524175)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top"
+ id="linearGradient38879"
+ x1="-1.9364917"
+ y1="-11.61895"
+ x2="1.9364917"
+ y2="-11.61895"
+ gradientTransform="scale(1.2909944,0.77459667)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top_center"
+ id="linearGradient38881"
+ x1="-1.4142137"
+ y1="-12.020815"
+ x2="1.4142137"
+ y2="-12.020815"
+ gradientTransform="scale(1.4142136,0.70710678)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top"
+ id="linearGradient38883"
+ x1="-2.236068"
+ y1="5.0311527"
+ x2="2.236068"
+ y2="5.0311527"
+ gradientTransform="scale(1.118034,0.89442719)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top_center"
+ id="linearGradient38885"
+ x1="-1.8708287"
+ y1="4.2761798"
+ x2="1.8708287"
+ y2="4.2761798"
+ gradientTransform="scale(1.069045,0.93541435)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top"
+ id="linearGradient38887"
+ x1="-1.0473186"
+ y1="10.741717"
+ x2="1.0473186"
+ y2="10.741717"
+ gradientTransform="scale(2.3870482,0.41892744)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ osb:paint="solid"
+ id="linearGradient4972">
+ <stop
+ id="stop4974"
+ offset="0"
+ style="stop-color:#d5d211;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ osb:paint="solid"
+ id="linearGradient4964">
+ <stop
+ id="stop4966"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2780">
+ <stop
+ offset="0"
+ style="stop-color:#ffffff"
+ id="stop2782" />
+ <stop
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0"
+ id="stop2784" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2834">
+ <stop
+ offset="0"
+ style="stop-color:#e59a00"
+ id="stop2836" />
+ <stop
+ offset="1"
+ style="stop-color:#faff7d;stop-opacity:.18367"
+ id="stop2838" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3057">
+ <stop
+ offset="0"
+ style="stop-color:#282828"
+ id="stop3059" />
+ <stop
+ offset="1"
+ style="stop-color:#282828;stop-opacity:0"
+ id="stop3061" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2713">
+ <stop
+ offset="0"
+ style="stop-color:#000000"
+ id="stop2715" />
+ <stop
+ offset="1"
+ style="stop-color:#ffffff"
+ id="stop2717" />
+ </linearGradient>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Greyscale"
+ id="filter40084">
+ <feColorMatrix
+ values="0.21 0.72 0.072 -0.3 0 0.21 0.72 0.072 -0.3 0 0.21 0.72 0.072 -0.3 0 0 0 0 1 0 "
+ id="feColorMatrix40082"
+ result="fbSourceGraphic" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix40328" />
+ <feColorMatrix
+ id="feColorMatrix40330"
+ values="0.21 0.72 0.072 0.3 0 0.21 0.72 0.072 0.3 0 0.21 0.72 0.072 0.3 0 0 0 0 1 0 "
+ in="fbSourceGraphic" />
+ </filter>
+ <filter
+ id="filter5183"
+ inkscape:label="Greyscale"
+ style="color-interpolation-filters:sRGB;">
+ <feColorMatrix
+ result="fbSourceGraphic"
+ id="feColorMatrix5181"
+ values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0" />
+ <feColorMatrix
+ id="feColorMatrix5185"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ in="fbSourceGraphic"
+ result="fbSourceGraphicAlpha" />
+ <feColorMatrix
+ result="fbSourceGraphic"
+ in="fbSourceGraphic"
+ values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"
+ id="feColorMatrix5187" />
+ <feColorMatrix
+ id="feColorMatrix5299"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ in="fbSourceGraphic"
+ result="fbSourceGraphicAlpha" />
+ <feColorMatrix
+ in="fbSourceGraphic"
+ values="0.21 0.72 0.072 -0.3 0 0.21 0.72 0.072 -0.3 0 0.21 0.72 0.072 -0.3 0 0 0 0 1 0 "
+ id="feColorMatrix5301" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1199"
+ inkscape:window-height="731"
+ id="namedview38328"
+ showgrid="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:zoom="14.75"
+ inkscape:cx="6.2137977"
+ inkscape:cy="8.1121029"
+ inkscape:window-x="46"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="layer5" />
+ <title
+ id="title38274">Highlight</title>
+ <g
+ inkscape:groupmode="layer"
+ id="layer5"
+ inkscape:label="Layer 1"
+ style="display:inline">
+ <g
+ id="add"
+ transform="matrix(1.3457681,0,0,1.3457681,8.0886149,9.9113851)"
+ style="filter:url(#filter40084)">
+ <defs
+ id="defs38291">
+ <linearGradient
+ id="top"
+ x1="0"
+ y1="0"
+ x2="1"
+ y2="0">
+ <stop
+ offset="0%"
+ style="stop-color:#e4ba2f;"
+ id="stop38276" />
+ <stop
+ offset="100%"
+ style="stop-color:#c36a00;"
+ id="stop38278" />
+ </linearGradient>
+ <linearGradient
+ id="top_center"
+ x1="0"
+ y1="0"
+ x2="1"
+ y2="0">
+ <stop
+ offset="0%"
+ style="stop-color:#f7e1c9;"
+ id="stop38281" />
+ <stop
+ offset="100%"
+ style="stop-color:#f4cfa5;"
+ id="stop38283" />
+ </linearGradient>
+ <linearGradient
+ id="hold"
+ x1="-3.3561783"
+ y1="-3.8734536"
+ x2="3.3561783"
+ y2="-3.8734536"
+ gradientTransform="scale(0.59591592,1.6780891)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0%"
+ style="stop-color:#f8edc3;"
+ id="stop38286" />
+ <stop
+ offset="100%"
+ style="stop-color:#f4d644;"
+ id="stop38288" />
+ </linearGradient>
+ </defs>
+ <g
+ transform="rotate(45)"
+ id="g38295">
+ <rect
+ width="5"
+ height="12.05"
+ transform="translate(-2.5,-7.5)"
+ style="fill:url(#linearGradient38877)"
+ id="rect38293"
+ x="0"
+ y="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38303">
+ <ellipse
+ cx="0"
+ cy="-7.5"
+ rx="2.5"
+ ry="1.5"
+ style="fill:url(#linearGradient38879)"
+ id="ellipse38297" />
+ <ellipse
+ cx="0"
+ cy="-7.5"
+ rx="2"
+ ry="1"
+ style="fill:url(#linearGradient38881)"
+ id="ellipse38299" />
+ <ellipse
+ cx="0"
+ cy="-7.5"
+ rx="0.75"
+ ry="0.34999999"
+ style="fill:#3d2c0c"
+ id="ellipse38301" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38307">
+ <path
+ d="m -2.5,4.5 2.5,4 2.5,-4 z"
+ style="fill:url(#linearGradient38883)"
+ id="path38305"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38311">
+ <path
+ d="M -2,4 0,7.5 2,4 Z"
+ style="fill:url(#linearGradient38885)"
+ id="path38309"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38315">
+ <path
+ d="m -2.5,4.5 a 4,4 0 0 0 5,0 z"
+ style="fill:url(#linearGradient38887)"
+ id="path38313"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38319">
+ <path
+ d="M -2,4 A 3,3 0 0 0 2,4 V -6.5 a 3.8,3.8 0 0 1 -4,0 z"
+ style="fill:url(#hold)"
+ id="path38317"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38323">
+ <path
+ d="M -1,7 0,8.5 1,7 a 2,2 0 0 1 -2,0 z"
+ style="fill:#3d2c0c"
+ id="path38321"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+ </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-linux/sign-inactive-18.svg
@@ -0,0 +1,383 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ version="1.1"
+ id="svg38326"
+ sodipodi:docname="sign-inactive.svg"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06">
+ <metadata
+ id="metadata38332">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title>Highlight</dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs38330">
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top"
+ id="linearGradient38877"
+ x1="0"
+ y1="0"
+ x2="7.7620873"
+ y2="0"
+ gradientTransform="scale(0.64415662,1.5524175)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top"
+ id="linearGradient38879"
+ x1="-1.9364917"
+ y1="-11.61895"
+ x2="1.9364917"
+ y2="-11.61895"
+ gradientTransform="scale(1.2909944,0.77459667)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top_center"
+ id="linearGradient38881"
+ x1="-1.4142137"
+ y1="-12.020815"
+ x2="1.4142137"
+ y2="-12.020815"
+ gradientTransform="scale(1.4142136,0.70710678)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top"
+ id="linearGradient38883"
+ x1="-2.236068"
+ y1="5.0311527"
+ x2="2.236068"
+ y2="5.0311527"
+ gradientTransform="scale(1.118034,0.89442719)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top_center"
+ id="linearGradient38885"
+ x1="-1.8708287"
+ y1="4.2761798"
+ x2="1.8708287"
+ y2="4.2761798"
+ gradientTransform="scale(1.069045,0.93541435)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top"
+ id="linearGradient38887"
+ x1="-1.0473186"
+ y1="10.741717"
+ x2="1.0473186"
+ y2="10.741717"
+ gradientTransform="scale(2.3870482,0.41892744)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ osb:paint="solid"
+ id="linearGradient4972">
+ <stop
+ id="stop4974"
+ offset="0"
+ style="stop-color:#d5d211;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ osb:paint="solid"
+ id="linearGradient4964">
+ <stop
+ id="stop4966"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2780">
+ <stop
+ offset="0"
+ style="stop-color:#ffffff"
+ id="stop2782" />
+ <stop
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0"
+ id="stop2784" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2834">
+ <stop
+ offset="0"
+ style="stop-color:#e59a00"
+ id="stop2836" />
+ <stop
+ offset="1"
+ style="stop-color:#faff7d;stop-opacity:.18367"
+ id="stop2838" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3057">
+ <stop
+ offset="0"
+ style="stop-color:#282828"
+ id="stop3059" />
+ <stop
+ offset="1"
+ style="stop-color:#282828;stop-opacity:0"
+ id="stop3061" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2713">
+ <stop
+ offset="0"
+ style="stop-color:#000000"
+ id="stop2715" />
+ <stop
+ offset="1"
+ style="stop-color:#ffffff"
+ id="stop2717" />
+ </linearGradient>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Greyscale"
+ id="filter40084">
+ <feColorMatrix
+ values="0.21 0.72 0.072 -0.3 0 0.21 0.72 0.072 -0.3 0 0.21 0.72 0.072 -0.3 0 0 0 0 1 0 "
+ id="feColorMatrix40082" />
+ </filter>
+ <filter
+ id="filter5183"
+ inkscape:label="Greyscale"
+ style="color-interpolation-filters:sRGB;">
+ <feColorMatrix
+ result="fbSourceGraphic"
+ id="feColorMatrix5181"
+ values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0" />
+ <feColorMatrix
+ id="feColorMatrix5185"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ in="fbSourceGraphic"
+ result="fbSourceGraphicAlpha" />
+ <feColorMatrix
+ result="fbSourceGraphic"
+ in="fbSourceGraphic"
+ values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"
+ id="feColorMatrix5187" />
+ <feColorMatrix
+ id="feColorMatrix5299"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ in="fbSourceGraphic"
+ result="fbSourceGraphicAlpha" />
+ <feColorMatrix
+ in="fbSourceGraphic"
+ values="0.21 0.72 0.072 -0.3 0 0.21 0.72 0.072 -0.3 0 0.21 0.72 0.072 -0.3 0 0 0 0 1 0 "
+ id="feColorMatrix5301" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1199"
+ inkscape:window-height="731"
+ id="namedview38328"
+ showgrid="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:zoom="14.75"
+ inkscape:cx="6.2137977"
+ inkscape:cy="8.1121029"
+ inkscape:window-x="46"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="layer6" />
+ <title
+ id="title38274">Highlight</title>
+ <g
+ inkscape:groupmode="layer"
+ id="layer5"
+ inkscape:label="Layer 1"
+ style="display:inline"
+ sodipodi:insensitive="true">
+ <g
+ id="add"
+ transform="matrix(1.3457681,0,0,1.3457681,8.0886149,9.9113851)"
+ style="filter:url(#filter40084)">
+ <defs
+ id="defs38291">
+ <linearGradient
+ id="top"
+ x1="0"
+ y1="0"
+ x2="1"
+ y2="0">
+ <stop
+ offset="0%"
+ style="stop-color:#e4ba2f;"
+ id="stop38276" />
+ <stop
+ offset="100%"
+ style="stop-color:#c36a00;"
+ id="stop38278" />
+ </linearGradient>
+ <linearGradient
+ id="top_center"
+ x1="0"
+ y1="0"
+ x2="1"
+ y2="0">
+ <stop
+ offset="0%"
+ style="stop-color:#f7e1c9;"
+ id="stop38281" />
+ <stop
+ offset="100%"
+ style="stop-color:#f4cfa5;"
+ id="stop38283" />
+ </linearGradient>
+ <linearGradient
+ id="hold"
+ x1="-3.3561783"
+ y1="-3.8734536"
+ x2="3.3561783"
+ y2="-3.8734536"
+ gradientTransform="scale(0.59591592,1.6780891)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0%"
+ style="stop-color:#f8edc3;"
+ id="stop38286" />
+ <stop
+ offset="100%"
+ style="stop-color:#f4d644;"
+ id="stop38288" />
+ </linearGradient>
+ </defs>
+ <g
+ transform="rotate(45)"
+ id="g38295">
+ <rect
+ width="5"
+ height="12.05"
+ transform="translate(-2.5,-7.5)"
+ style="fill:url(#linearGradient38877)"
+ id="rect38293"
+ x="0"
+ y="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38303">
+ <ellipse
+ cx="0"
+ cy="-7.5"
+ rx="2.5"
+ ry="1.5"
+ style="fill:url(#linearGradient38879)"
+ id="ellipse38297" />
+ <ellipse
+ cx="0"
+ cy="-7.5"
+ rx="2"
+ ry="1"
+ style="fill:url(#linearGradient38881)"
+ id="ellipse38299" />
+ <ellipse
+ cx="0"
+ cy="-7.5"
+ rx="0.75"
+ ry="0.34999999"
+ style="fill:#3d2c0c"
+ id="ellipse38301" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38307">
+ <path
+ d="m -2.5,4.5 2.5,4 2.5,-4 z"
+ style="fill:url(#linearGradient38883)"
+ id="path38305"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38311">
+ <path
+ d="M -2,4 0,7.5 2,4 Z"
+ style="fill:url(#linearGradient38885)"
+ id="path38309"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38315">
+ <path
+ d="m -2.5,4.5 a 4,4 0 0 0 5,0 z"
+ style="fill:url(#linearGradient38887)"
+ id="path38313"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38319">
+ <path
+ d="M -2,4 A 3,3 0 0 0 2,4 V -6.5 a 3.8,3.8 0 0 1 -4,0 z"
+ style="fill:url(#hold)"
+ id="path38317"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38323">
+ <path
+ d="M -1,7 0,8.5 1,7 a 2,2 0 0 1 -2,0 z"
+ style="fill:#3d2c0c"
+ id="path38321"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer6"
+ inkscape:label="Layer 2"
+ style="display:inline"
+ sodipodi:insensitive="true">
+ <g
+ id="layer3"
+ inkscape:label="Layer 4"
+ transform="matrix(1.0666804,0,0,1.0666446,0.0428691,-0.00298246)">
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:2.24979424;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 15.667684,15.742702 c -3.40903,-2.920537 -4.694046,-5.522759 -4.694046,-5.522759"
+ id="path4586"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:2.15561461;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 15.747607,10.026563 -5.09709,5.759107"
+ id="path5174"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ </g>
+ </g>
+</svg>
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..76e7e9ec79177e88c197e5874a8f4cc80ee69923
GIT binary patch
literal 3461
zc$@)+4SMp4P)<h;3K|Lk000e1NJLTq0015U0015c1^@s6J20-I000W3X+uL$Nkc;*
zP;zf(X>4Tx07%EJmUmDTc^1ci-IEhA3|YdElOUM^B*!5Ll0j648Nv{TabN%u6SBAh
zD~gCDWknZ36kL@cYd{PbSOgR>?;wkcaxMl$MdcbehP%4jKd$Ot*X#cDtJmLt9e!0`
z0LXnTK0g_j0w9H#E(r8=p@&CA(s6$P2-!%$4j8f6X?*8^0AKL8@o@<0MdGf}oEG@s
zg35_aV*>yMARNO<W2Yc|7Rh!tUyu#}YEDlxGo3%jdk~fpAOj+NbdKW}xP6Xe7Px1=
zCpgd*VG#g$GK(dM2Y@tz)ae=QctnRxM%aYM;qnk(iLe7ZfyF^M2jS%@DXDW@kFZ|M
zzv;yPhh~gelf{Y`^TM;t2i}dF#!qH#1pn_yNfs{NLjy^2g1{>fIU-*Ws!vMw5i>7l
zmH#4hIrIJVoFMcHS!8yaYvdwxSZ+RxEKCY<USyVF=~`|&GkB2&sexks*fjSbv3@Mm
zS3E=VDlx}#J(!D}oe&(h$Qj(wRg0XK6y&qi<0`HTgn{Cj;{+b!XG}?3nvKm`njt+Q
z*lTIFaPbVBST}cZofjhR=cl`fHTlT_;{MoVPjNjhBS?I0I`V@paw5w+U`Z!H{C+?X
z@B=Qu6xaX@U;@I@W3$rdhILKlZxnFj6VmC<$ZCnDGkI*|<#aO>Q%eBm*51OWeSd#`
zO+j+_r5Ybe8!GY-b}iLntO01N1VE9xRMV;fKoWUht&wb@AY-A1p4(vn5l8|Vpa@if
zCeQ;$zzkR+YupLA18?9Df<XjW2iPD1qyPcP1UX<k$OnaB57-YXK@B(p>cL5H8nl7)
z;4-)lZi7BB2p$0u7z3}tB=`tsAPB-i6o>{XLTZpUWC)o-49Ed;hkT(xC<2Ox;vpU+
zgmR!AP$5(TRYHfM2IyDl9CR7F3H3t{pb=;sdIx=hQ7{>nfmL7~cm>RWonaq17+wp<
z!yDjiI1k<pm&3L23Ah!$4Bv(aVG;Zqo<ac>2_=V8M;W0QC|8s}DiRfk5}>xA3Q%RJ
zT2vFN1J#YXixQzGP@mCQG!0Eh8=>vcUg!`s8@&O&1zm)$MAxHR(O1!T(Ie<d^mhye
zql_`c*kYKNa7;WV6O)fA!_;9~FxN1Hm@&*pEEX$=)x}z4y|5A3MC>MP5%wUq8QY2N
z$BtsBa5$VI&Jbsh^T)Ap>9~Ac1?~jy5^exDhWmsk;nnbFcn^Feo`=uHm*J1&FX9LA
zFYq%2DS|e^mf%m|5H=Eu2}cPXggb;W!VHm0)Fs*zgNcd6?Zk3o6Y(1HG4VZ#MA9JH
zkXDnpq-~^f(kapn(g^7@S(<D>b|puVh2-7jW8_Zq1M+(cg`z`oqC`^CDZ42Rlq-~{
zlur^g31bPS1V>`4M3qFV#DK&bNrI%dq_gBY$!y6o$up97BqyW@QaVzuQY@)0QdLst
zq=u!Ys5Gi6)t{O|EutQ$-lV>i#!2f)yGwJV^Q4bRUy&ZAp=g>kR~nnPgLag5jW#BO
zmC==9$|TAZ%AAzxm6?>4mbH)#mCcf^lD!}+l0(Vq$T8)T<%;D_%MHnWkynv-mXDR+
zCEp}JAV00JOu<PZR-r(lSz%D&iy~doT`@^<uVS0x6D72gfzoQFOr=9g*OVrf$t<&5
z#$Hyi?DVpS%CNG5a-i}i<s-^H%2O(;DxNBQl?s(BDif-5s!pnjs->zIRmat6Y7S~#
zwNkZ9YOm<BbZ2@py_|lPKB=y(?xikNKdj!XKBJ+l5u%Z+(X8=Alc>qijMFUD?9!an
zQq%I&%F$}ndZ<m%W@sm9AJFdBp4QRT3DeoB)28!MS3#Glo2}cZ`&f^n=cu<quU7A#
zK31QhpQL|Kzs~?Rurx?8s4}=?2pd`%at#j}_AkdQw^`0xUb}qQh-~C+lws6pG-51g
z>}#BB+-Ce{h1QD56?<27ulR0aX_9Pm)Z~$=w5hjgu4#wqJ2L|_wpo?gJ#&h=hxu0X
zcJsFuh8D3Fhb-<}(k%TfcUpE?ezUT+5?D1^jazG3M_X504>M#K0gNKX4I7M&i_KP>
z^ERJtt!&e6Puu=wXJnUTciisfO5K&QE03-mwb!s`*&ntSInW)V9e#EYIjTFd9BUn)
zIcYj^oa&rjIO{tnI-hWU<6`0>aA|RwcC~fg?Aqyua`SX6aO-uKb`No{bbsoh;SulA
z=rQSO>AA_X(+lI}<F(gon5n{KGaHzb-qzlmy|4O^eS&-{eV+Lm_zHYG{9r$CzkPm>
zS81=}t!ncJ{!IUU{!apQ12zQwwi>%SV0G2%=YghyIf30lw4n7t$Adlwy95^pKMK(c
z5r%YyN`^*-HiS-xxrLR4iNaTeZw|j5p%lT5Xph82hDO#!POWiYvwzK4lx0+Y)X-Yp
zwOMO#u2Wi<yzat!$@Q%DXQDCDVbRB<zq0&UwXCTaW=wU=B-@pJfc=`|#3|*B$J)o1
z#J-5Lk1L6L8E+rIFMd41F`+DBg6qny<i1VxN~}qoPV!GWmNc6jmfW0zPl--B$CKtI
z@vf$-re>w~^9}j=e9;Em4f{7t3VZ~0X;9kQwDxov#N_k{^@KkNM=~5Tsxm%hhGw40
zqGs{3dNvwt+_mvVwnz3ao6wuso4Rt;bGGM<Y<Av!cnh>8W=q#r&8>M`$F_NHtKUxC
zp0xe<T$9|A+z&g#c68*a<Za6v{lW8x#(ar<LH_VgyPY*ZqJQN6*t^SoSH-UH1)PFg
zg(ih%g<p%<MYn!3{i*z?+1+uwdyB1#5ADJ3;q4jP>$tbRgj$kaGP=)iUu!A7w4n6E
z{`LEBmRXe593UPL9uSrLmbX`ERqUzwTA5HeSmjdHT&-Nat9tq%=itC0r$bFODm8^Q
zUw-EPeE+cL;nrH6+Oi|qBN<1Y9}PX){R`ulhB~FX!n$wAc*jKbf%R7#tQ#7RD<3az
zgd2s8<0qm{^qq7&+1_N-bhufrx$qP?B|P=&*XUn|PWznhI>R{AbXNOpbqlSfpcQJ}
z*gDyk&?ah+XzxGgeXgs+uA}8Q<KK>*S3h5MLH0uNMdHQ0i?f%qFHLpwJ0~v3UmopR
z-}UH9_?3HCS6}VD=5y`Fb&u;;x}Cc(-LSuL{-*8CbGI0`+HPCjZtbz`Y5Cpq_m*C(
z-qt(TciQ`G`a1eo_Fo)u9JqYf?e6t^%zL*7R}J<Lg$&&vjv5x-=iGn!Ao;=DhZzrN
z9&LM!d0g;B>PgvC<)^iO=>O3yvJzbwaU1D*7WC}VD0}qvpJ{)79m{)8d0zHH?M1^&
zvzO<`J;wW9t$8)}n*VxcBL5%Me^kHGdvo?Lr@wk8!zahy3f|7XD|)Z^{@4eL4_#9M
zQ%^spe4Lps_@wx${xjoq_m{9QFJ>~o;=Y!D)Bo1--RJw`*_7E?K1;xww*n|6;kY;e
z-W>yg5&;0M4*;UXf;XNIXu;3V6Zkt`@XzyM1;CL`0D_$W@I{Ul+W^o(QW7~2aGI~f
z=H}x3SER+6n=NRO+7N3}Haq(s@q{>i0KN^+&d#*W&VH*yhJ6S?=f?$4J{P`50ATlr
zGHl#$@QVJ+8~+o2YXM}Bs!>h=000JJOGiWi{{a60|De66lK=n!32;bRa{vGf6951U
z69E94oEQKA00(qQO+^Ra3l0nh9fb98LjV8*V@X6oR7l6YmoaGKP#ng;XHFcd6|@}9
zBISm5=psEgT@(skI^?DfX6kT)aO*+0l7cQBg2Nq$V_Z)c5nM`3p%mPTR-xDy2OT14
zC@EUgcatISl-_Zd<Qu;{_~rlq-pl)ksH%#;(PT0~wOXZ@mzQX@T6lPPz<4}HzuzbL
z2fh2#g27<$GoQ~3l}ZIqPfxH*Z-P%xPiL|$4|QFK-C7EsPNxtAfnHx<1HgMYAkQ*r
zJRYAz8TouZ92^{=R4Sp<>3mUDmDpDC?(QzVzrSZZLI^}rgd|C1Wo5<wRv#T5(Pp#B
zEb#mNP!t7;M1t7aYBE1gy<V@ev$OM&Qp!LtE-uQENaT}a8E3QEO)HcT0!2~a1k?oU
zx{j->tAO=^D2leXcL6NRGPPFg^Z6i2l4DR4T&vX>QxF8$wR_(LKR!M(=KlUZoR*of
zRk2v?vnyb0z5oyohkv*N_Ika{*>yu600;(yjHzjwJK&9t4aU5=xoNlpjz*)5d3JWT
z=?XZROfqJ@UJuM>Gv~pnREmT`A=7+$c|oyQq^|7h!^1<<q?DpwucO=TW*h|rKqiwR
zYin!f%|t0hp-}kt{QT@JI2a5fm&+}Tw6ZMI;c%FDbmkHSfuz%Ev+DT_RxX#n-rnBY
zn)$-ab$onG48x%9cALrk?(Pn{uG6ipEn>&lZDGpfI1VQ#CuDDLkEwi~`)D-!ZWxAr
zGuJXT8V!1WevZ}ERq#BI_4Re|JP)4diO1t{?oT2Xi;?Z^ZM0e~I)B06Z!$+Jw(9lu
n6`H1f0DwRs(B(Mpzreo$fyw&*J0h@i00000NkvXXu0mjf3onty
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..7fe7fb542f47344bf71b319f326f617b43fc56b0
GIT binary patch
literal 235
zc%17D@N?(olHy`uVBq!ia0vp^oFL4?3?zm2ODh8@mUKs7M+Sy#H?H_c7y<c`C9V-A
z!TD(=<%vb942~)JNvR5+xryniL8*x;m4zo$ZGei@1AIbUV`C0M5K!Rc$&**FUVZT3
z!HX9!-o1PG>C>klKYslE`}hC<|5-uYzkn*3OM?7@Q`67zu<UyW<XU^WIEHY@CMN|2
z1tle1irShrl}Y&U-aYI*4xi*F{MrBOdllQVdM}?Mhfjb1mpcfmDp#^CGq3*k<|hNg
X?~|gl_b<`_xt_t()z4*}Q$iB}{##qC
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..df66d771c347df2fa658659e0c0882ba2fe0d39d
GIT binary patch
literal 232
zc%17D@N?(olHy`uVBq!ia0vp^oFL4?3?zm2ODh8@mUKs7M+Sy#H?H_c7y<c`C9V-A
z!TD(=<%vb942~)JNvR5+xryniL8*x;m4zo$ZGei@1AIbUV`C0M5K!Rc$&**FUVZT3
z!HX9!-o1PG>C>klKYslE`}hC<|5-uYzkn*3OM?7@Q`67zu<UyW<XU*TIEHXUCnp63
z1tle1irShrl}Y&U-fDh{1yBB8U47ly;BRoyB<4x)@9$^s($G9<Y_Rv{=JfOI42%_`
Vv){atuLGLF;OXk;vd$@?2>|rJS}*_r
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..561262f247bc25705b42eda6071f96d9aaff1024
GIT binary patch
literal 3833
zc$@+G4hHdwP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV000aUX+uL$Nkc;*
zP;zf(X>4Tx07%EJmv>at$ri`wS5gQGEf4`Ip*KN#mEJqj!A3{|gc3qRP_d(n3a(v5
zu%fFVimnBawPL}72-xd_h>Ef<7Hp`zNdo9|&;Iewd2`PE^2wch?|f%|GruzffTBH1
zAjn3Q0LbCz3!?*ln2AZrOwuDj1qdJmHDJPGiv-?LQDNX;%lli<7nbXml3d__E6N<M
zhz$S|1v!-?V&_0U0syYb76|hJAhbif!OVPtgx^6{6+(dk5Xllwm$8<FQ)O%|^@)x4
zh3p3anaW}b(*aPB<YyMJ(_tP30c2}FhsTG!9kK^IgT;Z|1$jbFPA+6cDrD2tAM&LC
zSI*S2oGe!Q7|X^YEo48QNRZ8%4gTLHCtEz)hd!*-3}H~TWXy2hs<U!KB@DGA$N8xd
zk&sm&U*vJ5{t~{IAr6Wg@$YAgeBqn{1^_7Fu>3+H!?{Em;;cCD5w>LsrLoW-jo{@6
z$BuBSFgJQ6KU%;Q`NxcM1~)itB<C`IcEl)eP2&XwLxw92JyMtvn;_GPo-5$RM?hA9
z+%C$B2_5l&RG8r#G3p}}M@uvtLVlMf42YKbVCp%d(Q#o`Y?f4mI%Kc>jMyMqESAU>
zC5DY^<8b}_Wg0LJmmfEx8H20KCwe5dSdbkx;*V|RW(P*g<{3LEDu@}4?aYS@Og0bL
z<4jgaluSSNUXULZE7OMKAPo2dKfnZHSW`hR$OOE${)T?|Pu3CuSU?EU0T)<~SVkif
zq%C~-i~)t<FGyS<8jJIhwsJuMw0|A5%AS@WP1;%@jmQE|A?M(lxCicrhry>8thTr_
z?lKz3R2=P<=uh+`1`z{@W@9?oP<1w}Lcsf{?RzBQDWQY#m~fNOMYsz>VJjDoS)!0X
zHnMo|M4Dyf{|sJ{@F(R=5DvOvT)@caQekx8s1I%oRXgB5xI5Gjwc`w26IkN*a3tQi
zCmfptwEn12Jf`CNT>jjjMBk5N9I-2yD>#g(NFCGfJLdIs7T+J{hy#B^fA++dVTZ96
z><o4ZJB~E~CUy#I#9FZv5*}L#Poyhhw6|!fwk(*3H|k?uYu#u4<sZGXMidC8?;GTE
zXXQ&agm123wvd;ek<avoUt}&bn9sJFz_hitu?Il%eV2Vc-ZG@$ZG>_8yDjfD04^oa
znEu_yG63M%Qvm4izuOGn!0ojffD`R(v9Lg9!zB*_pa4a<Gc<q>FaV}-Ot!!gxB)NV
z4?;jBhy_VtI-K<kkONiE1arY6Pz*}JYOn!RfNfwWs0RDNA<zJhgEQa)xB}Y2O>iHs
zjb88)41o7w2tg1MLPO|?2BM1?Ar^=&;*5A8{zw=SjU*u{NIJqt#K>G^F;a@GMJkXT
zNDcBkatt|xTtYgKyGS?EhrB^PqZmp>RZ(r!7@deZquyvJ8jDUt)6qP%5M6?<M9a}C
zbRSxeo<XmmH_<NiB|3-!OdexkdYC2VjQL`bSTdG|39<QD308(xVRcv&eAm~pF03E>
zgcET(&crR@`V7M3a5kQY&&OBb6?ipXkDtS@<K6fG{*6E*Xb~(3u7qGhA|ahHlTb`3
zBkU#|C7g#ls+aJdNF*|dCPXK=H<O5u$j#4W@F#M8tMVh?eUL?UUBEJ*I8ND_;b
zPbwyDBJCrcB6X5_Ngv7bWL>f?Ie?r@=97!aW#ql&ljKhFGxCs}lAMv8t6ZcUM{c&<
zD!E;9O>)=ddgX>F$`lidJ0+HqNm)cGr_@o-Qyx&>%FD~^%e%-=mgmVYlrNV*D1TAD
zTmBPOg=$XqrB0=asVk{_sHdrSsc&gCnla6bmQ2g1t)$h^&e9&yJ}S@^tQ3M3I0_3C
zDi!J#Iu%|i$|)KtdMi#>EL1F0Jgj(4v0q6}$ymu(iKR4OsZ!~f(ru+dWxBGBa-?#O
z@(Sg>%9oU%tB_QTRs2;rDoa#$s+?8np<{Fdx-XqgUrgUcKTq#fC90aJ2CHVOma6Vo
zZBrdkQ&F>5i&vYaR;hMEt&4#%j2Xd<Y{n|aA;vAn=W*KOyvK3JEgx4mu5;W6_3`Rn
z>Rj~_^#kfR)jw-6HT*TQG}dS|Xgt)!HO)0AYtGc%s(D_sf4u5=xAE-pCF2i`zo&(2
znQKLB&C%Ma)v7h9t)m^NEzsVieO9|)hoR%8lc}>_=cG=bE?w6{m#4d4_mu7nrW(_m
znawO`o?{N^Y3T*&iS>5qwdoD%o9M^s7wI3+e`r84a5hLYSZ~m5Fkq-_7;ZS%u-5RN
zk(`mUQHIe*qZXqN#wNxI#!HPGjGvpRn*^H_n$(&+Fr}G#n&z2SncgrXnmL<gnQbv^
zH%HAK%rnd@&97OY7LFD?i!Bx%69^MrCh#XzO}K4Iwe+?uu&lN0wqjU?Sru6|SiPEP
zI5BzRnu#qFzgat2XIt;GzHg&q6Jk?j(`56;*4&nDTWNd4j%F8Nx4^E^?zO#zJ=cD#
z{T&CoL%73Ihh~Sbj!uq3$2!M8Cqt(cr_D~comHKqoL4wsbRoF-xGZov?()gi(KX-o
zkn7)WmTp;Yd)=N-GMU7kv};n2yS_WieTRFO2h$_PW4lL}r=BOvv&!?amw^|@Yq!@k
zZ!_;q?|t5{e5`$hK1Y2%_`3Se^F8f{`33rw__g_~_{aHI_&*BJ4@eKF4HyV?2%Hmm
zI*1Sy8nh<pcCdCZJGdrzAjBzTUdXvnYG_PoMd;%&^DtppQ#cwP6230{L4;8RKcXQL
zL<UE$i+mVm5|tPA$7JH<sL7R+pGMn6&y8-0p~uXK*&Fjd)+csV?EN^?IB{Haykh*+
z_?q|+34RG{6S@*7CeBN|lBAi$OKMCeCC4Z4P9B`%KV`#|-l+~#i>Kb2W-@Kov`f=9
zr)N(;F+*_%YsTReLP|o)-jpw_NLCeVFf}-JbLs%wm%WkwlH<i$&*|g3bJud8rMaiA
zO?#g1p1v-<FT*pVETf<2%d6nM&J4=jmiZwoGHXxPaCSoWfgEy9O3pF93O|c~E>|aa
zR&IyDLQpK|%5%-zkT)O<748-xqG_VWd{ubVX%(A_e-U>Vcob9?e4H6S^YAR?S^Qb8
zv(0BOpZ%;bpzyaj_#F0}=DB)v7tQUS=RI%7d}My={N@D)3zjVCT^P8qdJ$z&)}pIL
z)<tWJ-Yrg8+_Xe{$-*T)zXbkLTdYtlEWW+eZRxgU_%hzIw&ixqH!c5G!YR32YF%1Z
z`eg-s#pPdZel7oXcxBqkwpEU+wyq|w=C8iB#&b>eTIIEcYkSs(ud83rTwk*O-G&((
zE|uArZQDrMDBjpr9#-DC$#B!^O<yW9DsEQ#R36x@wR!pG4_i1}I=6alt=p!(t#sSx
z?Y!-Gb_DLIuQIMG+ezG6u=DAz_+2f(IsaC(TXT2m?yr0Jd%CKltIyRq)zs|O+PkV2
ztrgey?VGx<eZSxS#yZQo9S0Z(N)LjA;)5@KPx<}Uq0mFkhn)}C9Wgqx`6&HpNj*|O
zyMCY{qoJ!Ysj=f&$g$=ox2B_iSpBi*xZd%~6KW?`our&xa&q`o;i<vXg46wH($DlX
z&uG4XHu3C@bCb`toew>K@j}3bvn}2&r!Kl*Jbuab(y`0Vmm97)Ua4<&Xgzw>;p)*g
z$F}-wPS+aSUD}&ECUu<b^z1xy-S7H^8^JfO+>E%{aVzfDo!e7ycirLKd44zh?(2I6
z_lE8-d_Z_m@=)nv*(0q-RezfOd7#U&>qNI-ckAPr$M<{KJujb#o_y(D@|5<p?3wPf
zn&-C9kM{-iwZEA1qW7iX<xqd|U&?=Ner5XV$lqRnw+<u@^u89p9)7dpt;X9u@9f_-
z4@M0>dY|)t=tIdzjgQrzoIkaEPWb$6XyzBvm-4S>Uz@&#etR&SGdwI{30cyk00t|X
zmIlC^Jpj;>;MoD*<0uNU`<JvJvOA-cz`wEVjwUUR0POJsAZ{T5VepKy6;>n2RQM}7
ze#d$N)Xr|KjucUvovkbv72aY9ABTtEY6C!O0^qA~cz9^b@bK4NQ0!d*PUp$)eUd_i
zXZ-EbbIw(RrT@5p{RjROYWNk`t^NQ200v@9M??Vs0RI60puMM)00009a7bBm000XU
z000XU0RWnu7ytkO2XskIMF-vt9S;N&AL>TU0007LNkl<ZILnoiUrfzm7{@=q-|rli
z$((a=bo^^M)J6UiE{fB3Hnuj)(rD6!=HHbY)?pZ%M$u-L3t<eiVP<Syl$FI2^QU7>
zG!tT{qeD(~aL(_2Tu3U-c&?uJ-S_=|_I=(5hN!McBLGTHWJ<Ks6$%~VzsnSv<lCDr
zjSq630DS${{BI9MIsy3Hlm93oYq8{Rbc>Q}MgK)tQtr;%kgCpKohFmA(&h0p&le>R
zEgivq|7Oq`UKPN-R7_bQ{-;wUZP^qFcl(wPrPcqWSLSXmX>wja`5bS-2kt!3v6@y|
zUesR^2_^P~s{c-Jx}WJ!@=O;~7D=4PE9<r;%j@=qk}%UNrmQR;thfAX-glJZ7%Rm~
z*Bqa5w$y^IMl)7_i+zM*Vv;a7^(8eoY*iw1PSdo=9}&hlw^P?}dC$9#R<biD05NQF
z3cF1M6qhP(6zqyevG%Bl7<~p2vju>t>?!e8gk7ihE8sJrNF4bcm+L3PY1dh?=nbWZ
z{c8Yi(BN(+UaX!I^{$>OVtPc(`6A|gx!as9!OnSo=Rhz+;xn_wl)}0J5yCwe)^92s
zX{kD3^wI%_v7_8dZCzI%$muZHSJ=iD-|Z~<zS9YyE2&SZZ>Txwo&Ac*;~fL$d#iOC
zH}6ZoyBI}zc8=7h7@wlAuK{Rn?Q6@D^8;I3&%7Z%e%Qchstet)f&4Zx3@cTqyZIRD
zregPwB^G~6+g3+H53@GgFiZnQ3{BI47%;BiZ^r-&3eXu2F*~d7-pkiMgiCUQfpD5G
vpfsRY0-$SxQu?6P6%-nA`Y6YEH^+Ve(rpoUN66bS00000NkvXXu0mjfGssP2
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-mac/attach-active-18.svg
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ version="1.1"
+ id="svg68075"
+ sodipodi:docname="attach-active-18.svg"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06">
+ <metadata
+ id="metadata68081">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs68079">
+ <linearGradient
+ id="linearGradient69547"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#eecb20;stop-opacity:1;"
+ offset="0"
+ id="stop69545" />
+ </linearGradient>
+ </defs>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1078"
+ inkscape:window-height="717"
+ id="namedview68077"
+ showgrid="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:zoom="15"
+ inkscape:cx="1.7362319"
+ inkscape:cy="3.9196061"
+ inkscape:window-x="166"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="layer2" />
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="Layer 2"
+ style="display:inline">
+ <path
+ sodipodi:nodetypes="czzzzzzc"
+ inkscape:connector-curvature="0"
+ id="path77072"
+ d="m 6.8194899,3.6676829 c 0,0 -1.5664037,1.9124961 -4.8594892,5.5898643 C -1.3330856,12.934914 4.4611524,19.40164 7.4269247,16.137379 10.392697,12.873119 14.121702,8.6857836 16.34864,6.1186233 18.575582,3.551463 14.758628,-0.78139789 12.476236,1.7327302 10.193845,4.2468574 6.3806119,8.6385171 4.9212518,10.289521 3.4618907,11.940526 5.6504624,14.00041 6.9713487,12.525467 8.292233,11.050523 13.767037,4.9146521 13.767037,4.9146521"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#f0b305;stroke-width:1.95830035;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer1"
+ inkscape:label="Layer 1"
+ style="display:none"
+ transform="translate(-22.386738,-79.656408)"
+ sodipodi:insensitive="true">
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#fad938;stroke-width:0.80000001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 6.8194899,3.6676829 c 0,0 -1.5664037,1.9124961 -4.8594892,5.5898643 C -1.3330856,12.934914 4.4611524,19.40164 7.4269247,16.137379 10.392697,12.873119 14.121702,8.6857836 16.34864,6.1186233 18.575582,3.551463 14.758628,-0.78139789 12.476236,1.7327302 10.193845,4.2468574 6.3806119,8.6385171 4.9212518,10.289521 3.4618907,11.940526 5.6504624,14.00041 6.9713487,12.525467 8.292233,11.050523 13.767037,4.9146521 13.767037,4.9146521"
+ id="path77076"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="czzzzzzc"
+ transform="translate(22.386738,79.656408)" />
+ </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-mac/attach-disabled-18.svg
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ version="1.1"
+ id="svg68075"
+ sodipodi:docname="attach-disabled-18.svg"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06">
+ <metadata
+ id="metadata68081">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs68079">
+ <linearGradient
+ id="linearGradient69547"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#eecb20;stop-opacity:1;"
+ offset="0"
+ id="stop69545" />
+ </linearGradient>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Greyscale"
+ id="filter76500">
+ <feColorMatrix
+ values="0.21 0.72 0.072 0.1 0 0.21 0.72 0.072 0.1 0 0.21 0.72 0.072 0.1 0 0 0 0 1 0 "
+ id="feColorMatrix76498" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1078"
+ inkscape:window-height="717"
+ id="namedview68077"
+ showgrid="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:zoom="15"
+ inkscape:cx="5.0884354"
+ inkscape:cy="3.9196061"
+ inkscape:window-x="115"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="layer1" />
+ <g
+ inkscape:groupmode="layer"
+ id="layer1"
+ inkscape:label="Layer 1"
+ style="display:inline"
+ transform="translate(-22.386738,-79.656408)">
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#fabe14;stroke-width:1.6147449;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;filter:url(#filter76500)"
+ d="m 29.159662,83.21089 c 0,0 -1.599889,1.953061 -4.963371,5.708428 -3.363483,3.755365 2.554619,10.359254 5.583791,7.025756 3.029172,-3.333496 6.837892,-7.609647 9.112436,-10.231258 2.274547,-2.621611 -1.624002,-7.046374 -3.955184,-4.47892 -2.331183,2.567453 -6.225932,7.052262 -7.716489,8.738285 -1.490558,1.686023 0.744799,3.789598 2.093922,2.283371 1.349121,-1.506228 6.940961,-7.772244 6.940961,-7.772244"
+ id="path68084"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="czzzzzzc" />
+ </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-mac/attach-inactive-18.svg
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ version="1.1"
+ id="svg68075"
+ sodipodi:docname="attach-inactive-18.svg"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06">
+ <metadata
+ id="metadata68081">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs68079">
+ <linearGradient
+ id="linearGradient69547"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#eecb20;stop-opacity:1;"
+ offset="0"
+ id="stop69545" />
+ </linearGradient>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Greyscale"
+ id="filter74568">
+ <feColorMatrix
+ values="0.21 0.72 0.072 -0.2 0 0.21 0.72 0.072 -0.2 0 0.21 0.72 0.072 -0.2 0 0 0 0 1 0 "
+ id="feColorMatrix74566" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1078"
+ inkscape:window-height="717"
+ id="namedview68077"
+ showgrid="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:zoom="15"
+ inkscape:cx="5.0884354"
+ inkscape:cy="3.9196061"
+ inkscape:window-x="115"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="layer1" />
+ <g
+ inkscape:groupmode="layer"
+ id="layer1"
+ inkscape:label="Layer 1"
+ style="display:inline"
+ transform="translate(-22.386738,-79.656408)">
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#fabe14;stroke-width:1.6147449;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;filter:url(#filter74568)"
+ d="m 29.159662,83.21089 c 0,0 -1.599889,1.953061 -4.963371,5.708428 -3.363483,3.755365 2.554619,10.359254 5.583791,7.025756 3.029172,-3.333496 6.837892,-7.609647 9.112436,-10.231258 2.274547,-2.621611 -1.624002,-7.046374 -3.955184,-4.47892 -2.331183,2.567453 -6.225932,7.052262 -7.716489,8.738285 -1.490558,1.686023 0.744799,3.789598 2.093922,2.283371 1.349121,-1.506228 6.940961,-7.772244 6.940961,-7.772244"
+ id="path68084"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="czzzzzzc" />
+ </g>
+</svg>
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2c06f3de40554d0f234e936f16d2352d93ed30ac
GIT binary patch
literal 1027
zc$@(P1pNDnP)<h;3K|Lk000e1NJLTq001BW000mO1^@s6cL04^00001b5ch_0Itp)
z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2ipM(
z5)TXM7(KxN00V<bL_t(Y$Gz2GY*SSn2k`GX_uSoE+I!pEHLS~6c4yN$EQ3M^7DTtn
z(6OkA2@(@c%uIaXMIU`pS4;?zR}=TRN0K2A{(+E~tc!ofz(q4c_Qx)ElL2d2HrkGM
zx4risA8<NE*DfUdlJoRC=X}07IXQ=*LQTb5b#9W6Kp+vqhjxBC{SCHQ>5CzMp==+t
zO2%;D#P8XSJyHp$qCDb_KHJ0;1@~uXkx%><+u1*Ps#<(<a7WPT3yy30dundsyVbji
z%e{Nvzw^U-pZ+g?CahD*F;e&PD@~3@70hu5-so;<k!Wn{^sfGDahJbsj5$1NdZmO*
z<J6`44__SrP;+k@VEVJC6-voSUC;3*=CNSrCtzE76c=wmX={ZRIUJigv%9|<+!yNh
zDvcos=^-3Bv=3_iL8V+SbZmYC#L?r}@l=!SuZNZW6@<A!nTp(G8u^)@;c07!dv8Z<
z`fT`vN;qJG$r8Mw7Ld$2Ec`T(7uJkxn+6aBfDi$M$bhJT3MvS5qxR4Nh>8ccm4~;z
z7d3A0CzWvU5;!kGkZQoWv22%9kpo{~aeY4fuK=`0m|n{yVOugpcNDg<076}0%mq;i
zfG`zAR3JFro@#Jz8%SM?FXs|7(Y+t%5*yb%5@2c|s&mW!T8lgn@LLeo{V)p(xLE*U
z3W%tJNR24nTSDpH?e8B4e<VP1AgWtHTx;*vw>8v(+a;K}1c)mP$}<2ssN4uMn?Y*$
z^P5Z{eUF0!fUps_MH1H{y}F~m7TnANwg>4e7f}4;7dQj2f+<0ib4yqpK7Eti&%V%n
za%_&rJDDLhT=DJdZrCdR_d}E%jOv^VaV`9sF4fk+Hg1EddypDA2fMfu14oe)8e^{Q
z4s&&Bo}k>>+&4A{0JB4_5!o5Is)afzAqLqB4gfSc*r|hxac|@eU9P3DR&Rj0+8}Ie
zfo){b+&6lv`Q6d8mRR=N#mqBB=W@$B4gmJr+H(%yOG0}5GO^1B-wF-@jEGj;6Mjn<
zop(`M8H2R_buf42!SM+Hywt(BujT;c{=LEVq;&k){vd4MQOsYuU=x|o8L7mvYVZe_
zkWt+g=s|ws1f+&OFzrPweD%&>U0QL2FXO3wR{rFV0cB;#ESjT5xiHZcgIV=YHGjT~
xTcxFk#{Bs%jwe)%2o$%zee>a)DbOe7<4*_U!GowNcjf>9002ovPDHLkV1lWK)K~xj
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-mac/decrypt-active-18.svg
@@ -0,0 +1,168 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ viewBox="0 0 16.874999 16.875"
+ id="svg3423"
+ version="1.1"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06"
+ sodipodi:docname="decrypt-active-18.svg"
+ inkscape:export-filename="/Users/pbr/enigmail/Logo/Enigmail Icon V3 16x16.png"
+ inkscape:export-xdpi="2.6199999"
+ inkscape:export-ydpi="2.6199999">
+ <defs
+ id="defs3425">
+ <linearGradient
+ id="linearGradient4972"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#d5d211;stop-opacity:1;"
+ offset="0"
+ id="stop4974" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4964"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4966" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2780">
+ <stop
+ id="stop2782"
+ style="stop-color:#ffffff"
+ offset="0" />
+ <stop
+ id="stop2784"
+ style="stop-color:#ffffff;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2834">
+ <stop
+ id="stop2836"
+ style="stop-color:#e59a00"
+ offset="0" />
+ <stop
+ id="stop2838"
+ style="stop-color:#faff7d;stop-opacity:.18367"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3057">
+ <stop
+ id="stop3059"
+ style="stop-color:#282828"
+ offset="0" />
+ <stop
+ id="stop3061"
+ style="stop-color:#282828;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2713">
+ <stop
+ id="stop2715"
+ style="stop-color:#000000"
+ offset="0" />
+ <stop
+ id="stop2717"
+ style="stop-color:#ffffff"
+ offset="1" />
+ </linearGradient>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="20"
+ inkscape:cx="-3.2745776"
+ inkscape:cy="8.1578366"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer2"
+ showgrid="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1163"
+ inkscape:window-height="728"
+ inkscape:window-x="45"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ units="px"
+ inkscape:snap-grids="false">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4919"
+ originx="-299.99048"
+ originy="-124.88544" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata3428">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-299.99051,-910.60174)"
+ style="display:inline">
+ <rect
+ style="fill:#000000;fill-opacity:1;stroke:#080808;stroke-width:3.075562;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;image-rendering:auto"
+ id="rect4872"
+ width="7.236938"
+ height="7.236938"
+ x="302.46579"
+ y="918.70203"
+ rx="0.008277542"
+ ry="0.044642854" />
+ <rect
+ ry="0.0078140106"
+ rx="0.00144885"
+ y="921.68713"
+ x="305.4509"
+ height="1.2667092"
+ width="1.2667092"
+ id="rect4419"
+ style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:2.8125;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:14.39999962;stroke-dasharray:none;stroke-opacity:1;image-rendering:auto" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="Layer 2"
+ style="display:inline"
+ transform="translate(-299.99051,-602.33402)">
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2.00754595;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 308.94445,609.1247 c 0,0 0.0159,-2.38773 0.0784,-3.49702 0.0816,-1.44553 1.96247,-2.34204 3.19909,-2.22236 1.2066,0.11676 2.67091,1.33032 2.77304,2.73882 0.0866,1.19436 0.094,1.94763 0.094,1.94763"
+ id="path1498"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="csasc" />
+ </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-mac/decrypt-inactive-18.svg
@@ -0,0 +1,168 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ viewBox="0 0 16.874999 16.875"
+ id="svg3423"
+ version="1.1"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06"
+ sodipodi:docname="decrypt-inactive-18.svg"
+ inkscape:export-filename="/Users/pbr/enigmail/Logo/Enigmail Icon V3 16x16.png"
+ inkscape:export-xdpi="2.6199999"
+ inkscape:export-ydpi="2.6199999">
+ <defs
+ id="defs3425">
+ <linearGradient
+ id="linearGradient4972"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#d5d211;stop-opacity:1;"
+ offset="0"
+ id="stop4974" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4964"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4966" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2780">
+ <stop
+ id="stop2782"
+ style="stop-color:#ffffff"
+ offset="0" />
+ <stop
+ id="stop2784"
+ style="stop-color:#ffffff;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2834">
+ <stop
+ id="stop2836"
+ style="stop-color:#e59a00"
+ offset="0" />
+ <stop
+ id="stop2838"
+ style="stop-color:#faff7d;stop-opacity:.18367"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3057">
+ <stop
+ id="stop3059"
+ style="stop-color:#282828"
+ offset="0" />
+ <stop
+ id="stop3061"
+ style="stop-color:#282828;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2713">
+ <stop
+ id="stop2715"
+ style="stop-color:#000000"
+ offset="0" />
+ <stop
+ id="stop2717"
+ style="stop-color:#ffffff"
+ offset="1" />
+ </linearGradient>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="20"
+ inkscape:cx="5.7254224"
+ inkscape:cy="8.1578366"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer2"
+ showgrid="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1163"
+ inkscape:window-height="728"
+ inkscape:window-x="45"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ units="px"
+ inkscape:snap-grids="false">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4919"
+ originx="-299.99048"
+ originy="-124.88544" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata3428">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-299.99051,-910.60174)"
+ style="display:inline">
+ <rect
+ style="fill:#4d4d4d;fill-opacity:1;stroke:#4d4d4d;stroke-width:3.075562;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;image-rendering:auto"
+ id="rect4872"
+ width="7.236938"
+ height="7.236938"
+ x="302.46579"
+ y="918.70203"
+ rx="0.008277542"
+ ry="0.044642854" />
+ <rect
+ ry="0.0078140106"
+ rx="0.00144885"
+ y="921.68713"
+ x="305.4509"
+ height="1.2667092"
+ width="1.2667092"
+ id="rect4419"
+ style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:2.8125;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:14.39999962;stroke-dasharray:none;stroke-opacity:1;image-rendering:auto" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="Layer 2"
+ style="display:inline"
+ transform="translate(-299.99051,-602.33402)">
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#4d4d4d;stroke-width:2.00754595;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 308.94445,609.1247 c 0,0 0.0159,-2.38773 0.0784,-3.49702 0.0816,-1.44553 1.96247,-2.34204 3.19909,-2.22236 1.2066,0.11676 2.67091,1.33032 2.77304,2.73882 0.0866,1.19436 0.094,1.94763 0.094,1.94763"
+ id="path1498"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="csasc" />
+ </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-mac/encrypt-active-18.svg
@@ -0,0 +1,297 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ viewBox="0 0 16.874999 16.875"
+ id="svg3423"
+ version="1.1"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06"
+ sodipodi:docname="encrypt-active.svg"
+ inkscape:export-filename="/Users/pbr/enigmail/Logo/Enigmail Icon V3 16x16.png"
+ inkscape:export-xdpi="2.6199999"
+ inkscape:export-ydpi="2.6199999">
+ <defs
+ id="defs3425">
+ <linearGradient
+ id="linearGradient4972"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#d5d211;stop-opacity:1;"
+ offset="0"
+ id="stop4974" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4964"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4966" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4983"
+ y2="11.711"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="35.995998"
+ gradientTransform="matrix(7.1505593,0,0,8.534094,228.71232,184.93067)"
+ y1="1.6246001"
+ x1="35.995998"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient2780">
+ <stop
+ id="stop2782"
+ style="stop-color:#ffffff"
+ offset="0" />
+ <stop
+ id="stop2784"
+ style="stop-color:#ffffff;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4980"
+ y2="6.0918002"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="39.723999"
+ gradientTransform="matrix(7.1746573,0,0,8.6675651,228.71232,177.84541)"
+ y1="11.17"
+ x1="38.362999"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient2834">
+ <stop
+ id="stop2836"
+ style="stop-color:#e59a00"
+ offset="0" />
+ <stop
+ id="stop2838"
+ style="stop-color:#faff7d;stop-opacity:.18367"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4971"
+ y2="7.5862999"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="38.782001"
+ gradientTransform="matrix(7.1505593,0,0,8.6966416,228.71232,177.84541)"
+ y1="9.5746002"
+ x1="38.782001"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4968"
+ y2="-13.17"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="33.183998"
+ gradientTransform="matrix(9.6839253,0,0,6.8812942,222.29523,170.28389)"
+ y1="24.385"
+ x1="33.183998"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4965"
+ y2="-13.17"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="33.183998"
+ gradientTransform="matrix(9.4687954,0,0,5.4755733,228.71232,182.51659)"
+ y1="-4.6247249"
+ x1="33.003456"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3057">
+ <stop
+ id="stop3059"
+ style="stop-color:#282828"
+ offset="0" />
+ <stop
+ id="stop3061"
+ style="stop-color:#282828;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3151"
+ y2="52.609001"
+ spreadMethod="reflect"
+ gradientUnits="userSpaceOnUse"
+ x2="44.723999"
+ gradientTransform="matrix(9.9301464,0,0,6.770848,212.4891,154.91826)"
+ y1="52.609001"
+ x1="29.614"
+ inkscape:collect="always">
+ <stop
+ id="stop2703"
+ style="stop-color:#db9300"
+ offset="0" />
+ <stop
+ id="stop2770"
+ style="stop-color:#fac700"
+ offset=".75" />
+ <stop
+ id="stop2705"
+ style="stop-color:#fff363"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2713">
+ <stop
+ id="stop2715"
+ style="stop-color:#000000"
+ offset="0" />
+ <stop
+ id="stop2717"
+ style="stop-color:#ffffff"
+ offset="1" />
+ </linearGradient>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="25"
+ inkscape:cx="6.7707505"
+ inkscape:cy="8.9438348"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1239"
+ inkscape:window-height="755"
+ inkscape:window-x="45"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ units="px">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4919"
+ originx="-284.74604"
+ originy="-71.484602" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata3428">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="Layer 2"
+ style="display:inline"
+ transform="translate(-284.74607,-655.73485)"
+ sodipodi:insensitive="true">
+ <g
+ transform="matrix(0.03532479,0,0,0.03960741,274.23987,648.41157)"
+ inkscape:label="Layer 2"
+ id="layer2-4">
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,184.8967 c -87.94487,0 -158.74505,56.60787 -158.74505,126.92368 v 141.02397 h 44.09513 V 311.82038 c 0,-50.7856 51.13613,-91.66768 114.64992,-91.66768 63.51378,0 114.64993,40.88208 114.64993,91.66768 V 452.84435 H 669.6692 V 311.82038 c 0,-70.31581 -70.80018,-126.92368 -158.74505,-126.92368 z"
+ style="fill:#282828;fill-rule:evenodd;stroke-width:3.01772833"
+ id="rect2723" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,194.10762 c -83.05952,0 -149.92515,57.12633 -149.92515,122.29246 v 131.4673 c 0,49.3598 38.45179,91.27181 93.15445,108.75267 C 414.96119,542.36212 387.4587,511.79338 387.4587,475.54598 V 309.48131 c 0,-49.83274 51.13176,-93.25814 114.64554,-93.25814 h 17.6398 c 63.51379,0 114.64994,43.4254 114.64994,93.25814 v 166.06467 c 0,36.2474 -27.5025,66.81614 -66.69476,81.07407 54.69829,-17.48086 93.15007,-59.39287 93.15007,-108.75267 v -131.4673 c 0,-65.16613 -66.86561,-122.29246 -149.92514,-122.29246 z"
+ style="opacity:0.65;fill:url(#linearGradient4983);fill-rule:evenodd;stroke-width:3.01772833"
+ id="path3082" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,187.88807 c -85.48249,0 -154.30223,54.6531 -154.30223,122.5412 v 136.95677 c 0,51.41616 39.57782,95.07978 95.87093,113.29631 -40.33581,-14.85349 -68.6401,-46.70438 -68.6401,-84.46164 v -173.001 c 0,-51.91359 52.62584,-101.87944 117.99736,-101.87944 l 18.15245,-3.4e-4 c 65.36715,0 117.99298,49.96583 117.99298,101.87944 v 173.00099 c 0,37.75727 -28.3043,69.60817 -68.64012,84.46163 56.29315,-18.21653 95.87096,-61.88014 95.87096,-113.2963 V 310.4289 c 0,-67.8881 -68.81976,-122.54118 -154.30223,-122.54118 z"
+ style="opacity:0.5;fill:url(#linearGradient4980);fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2931" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,199.88821 c -83.05952,0 -149.92515,45.52381 -149.92515,111.93356 v 133.97558 c 0,50.29514 38.45179,92.99187 93.15445,110.82307 C 414.96119,542.08221 387.4587,510.93897 387.4587,474.00145 V 304.76987 c 0,-50.78207 51.13176,-91.72024 114.64554,-91.72024 h 17.6398 c 63.51379,0 114.64994,40.93817 114.64994,91.72024 v 169.23158 c 0,36.93752 -27.5025,68.08076 -66.69476,82.61897 54.69829,-17.8312 93.15007,-60.52793 93.15007,-110.82307 V 311.82177 c 0,-66.40975 -66.86561,-111.93356 -149.92514,-111.93356 z"
+ style="opacity:0.5;fill:url(#linearGradient4971);fill-rule:evenodd;stroke-width:3.01772833"
+ id="rect2875" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,192.03933 c -117.25692,0 -144.31249,81.67662 -144.31249,118.61765 0,110.81954 9.01707,110.81954 9.01707,0 0,-81.27025 72.13215,-113.34537 135.29542,-113.34537 63.16326,7e-4 135.2954,35.39264 135.2954,113.34537 0,110.81954 9.02146,110.81954 9.02146,0 0,-36.94103 -27.05996,-118.61765 -144.31686,-118.61765 z"
+ style="opacity:0.8;fill:url(#linearGradient4968);fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2933" />
+ <rect
+ x="323.9566"
+ y="357.59714"
+ width="371.55212"
+ height="253.34859"
+ ry="1.9118049"
+ rx="2.3074045"
+ style="fill:url(#linearGradient3151);fill-rule:evenodd;stroke-width:3.01772833"
+ id="rect1941" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 331.08188,610.95331 c 8.3528,-8.10605 83.73569,-14.49145 178.38542,-14.49145 93.35299,0 167.50834,6.30892 177.61479,14.26203 -0.18461,0 -0.13845,0.22942 -0.27689,0.22942 z"
+ style="opacity:0.5;fill:#000000;fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2768" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,197.15513 c -114.64992,0 -141.10525,67.66709 -141.10525,97.06436 0,88.18571 8.81991,88.18571 8.81991,0 0,-64.66912 70.52414,-92.86779 132.28534,-92.86779 61.76122,6.1e-4 132.28972,30.83556 132.28972,92.86779 0,88.18571 8.81553,88.18571 8.81553,0 0,-29.39727 -26.45532,-97.06436 -141.10525,-97.06436 z"
+ style="opacity:1;fill:url(#linearGradient4965);fill-opacity:1;fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2885" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 345.56746,357.59712 c 7.93046,8.11368 79.50178,14.47618 169.36574,14.47618 88.63275,0 159.02983,-6.29748 168.62525,-14.2506 -0.13145,-0.0122 -0.13145,-0.22558 -0.26289,-0.22558 z"
+ style="opacity:0.5;fill:#ffffff;fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2727" />
+ </g>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer1"
+ inkscape:label="Layer 3"
+ sodipodi:insensitive="true">
+ <g
+ id="g5151"
+ transform="translate(-1.0275364)">
+ <path
+ inkscape:connector-curvature="0"
+ id="path5137"
+ d="M 9.8625001,9.3749997 7.1249998,9.4124997 7.1624998,14.1 9.8250001,14.0625"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:0.93749994;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5139"
+ d="M 9.9750001,11.6438 7.4249998,11.6813"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#0000ff;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:0.93749994;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ </g>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer3"
+ inkscape:label="Layer 4"
+ sodipodi:insensitive="true">
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#008000;stroke-width:1.74388254;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 11.90849,12.21419 c 0,0 1.39836,1.035106 2.568841,2.4924 0.454732,-3.871985 1.725652,-6.1303502 1.725652,-6.1303502"
+ id="path4586"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccc" />
+ </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-mac/encrypt-disabled-18.svg
@@ -0,0 +1,322 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ viewBox="0 0 16.874999 16.875"
+ id="svg3423"
+ version="1.1"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06"
+ sodipodi:docname="encrypt-disabled.svg"
+ inkscape:export-filename="/Users/pbr/enigmail/Logo/Enigmail Icon V3 16x16.png"
+ inkscape:export-xdpi="2.6199999"
+ inkscape:export-ydpi="2.6199999">
+ <defs
+ id="defs3425">
+ <linearGradient
+ id="linearGradient4972"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#d5d211;stop-opacity:1;"
+ offset="0"
+ id="stop4974" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4964"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4966" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4983"
+ y2="11.711"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="35.995998"
+ gradientTransform="matrix(7.1505593,0,0,8.534094,228.71232,184.93067)"
+ y1="1.6246001"
+ x1="35.995998"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient2780">
+ <stop
+ id="stop2782"
+ style="stop-color:#ffffff"
+ offset="0" />
+ <stop
+ id="stop2784"
+ style="stop-color:#ffffff;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4980"
+ y2="6.0918002"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="39.723999"
+ gradientTransform="matrix(7.1746573,0,0,8.6675651,228.71232,177.84541)"
+ y1="11.17"
+ x1="38.362999"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient2834">
+ <stop
+ id="stop2836"
+ style="stop-color:#e59a00"
+ offset="0" />
+ <stop
+ id="stop2838"
+ style="stop-color:#faff7d;stop-opacity:.18367"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4971"
+ y2="7.5862999"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="38.782001"
+ gradientTransform="matrix(7.1505593,0,0,8.6966416,228.71232,177.84541)"
+ y1="9.5746002"
+ x1="38.782001"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4968"
+ y2="-13.17"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="33.183998"
+ gradientTransform="matrix(9.6839253,0,0,6.8812942,222.29523,170.28389)"
+ y1="24.385"
+ x1="33.183998"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4965"
+ y2="-13.17"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="33.183998"
+ gradientTransform="matrix(9.4687954,0,0,5.4755733,228.71232,182.51659)"
+ y1="-4.6247249"
+ x1="33.003456"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3057">
+ <stop
+ id="stop3059"
+ style="stop-color:#282828"
+ offset="0" />
+ <stop
+ id="stop3061"
+ style="stop-color:#282828;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3151"
+ y2="52.609001"
+ spreadMethod="reflect"
+ gradientUnits="userSpaceOnUse"
+ x2="44.723999"
+ gradientTransform="matrix(9.9301464,0,0,6.770848,212.4891,154.91826)"
+ y1="52.609001"
+ x1="29.614"
+ inkscape:collect="always">
+ <stop
+ id="stop2703"
+ style="stop-color:#db9300"
+ offset="0" />
+ <stop
+ id="stop2770"
+ style="stop-color:#fac700"
+ offset=".75" />
+ <stop
+ id="stop2705"
+ style="stop-color:#fff363"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2713">
+ <stop
+ id="stop2715"
+ style="stop-color:#000000"
+ offset="0" />
+ <stop
+ id="stop2717"
+ style="stop-color:#ffffff"
+ offset="1" />
+ </linearGradient>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Greyscale"
+ id="filter5183">
+ <feColorMatrix
+ values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"
+ id="feColorMatrix5181"
+ result="fbSourceGraphic" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix5185" />
+ <feColorMatrix
+ id="feColorMatrix5187"
+ values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"
+ in="fbSourceGraphic"
+ result="fbSourceGraphic" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix5299" />
+ <feColorMatrix
+ id="feColorMatrix5301"
+ values="0.21 0.72 0.072 -0.3 0 0.21 0.72 0.072 -0.3 0 0.21 0.72 0.072 -0.3 0 0 0 0 1 0 "
+ in="fbSourceGraphic"
+ result="fbSourceGraphic" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix5380" />
+ <feColorMatrix
+ id="feColorMatrix5382"
+ values="0.21 0.72 0.072 0.4 0 0.21 0.72 0.072 0.4 0 0.21 0.72 0.072 0.4 0 0 0 0 1 0 "
+ in="fbSourceGraphic" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="25"
+ inkscape:cx="5.0307505"
+ inkscape:cy="7.0342183"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer2"
+ showgrid="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1239"
+ inkscape:window-height="755"
+ inkscape:window-x="45"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ units="px">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4919"
+ originx="-284.74604"
+ originy="-71.484602" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata3428">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="Layer 2"
+ style="display:inline"
+ transform="translate(-284.74607,-655.73485)">
+ <g
+ transform="matrix(0.03532479,0,0,0.03960741,274.23987,648.41157)"
+ inkscape:label="Layer 2"
+ id="layer2-4"
+ style="filter:url(#filter5183)">
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,184.8967 c -87.94487,0 -158.74505,56.60787 -158.74505,126.92368 v 141.02397 h 44.09513 V 311.82038 c 0,-50.7856 51.13613,-91.66768 114.64992,-91.66768 63.51378,0 114.64993,40.88208 114.64993,91.66768 V 452.84435 H 669.6692 V 311.82038 c 0,-70.31581 -70.80018,-126.92368 -158.74505,-126.92368 z"
+ style="fill:#282828;fill-rule:evenodd;stroke-width:3.01772833"
+ id="rect2723" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,194.10762 c -83.05952,0 -149.92515,57.12633 -149.92515,122.29246 v 131.4673 c 0,49.3598 38.45179,91.27181 93.15445,108.75267 C 414.96119,542.36212 387.4587,511.79338 387.4587,475.54598 V 309.48131 c 0,-49.83274 51.13176,-93.25814 114.64554,-93.25814 h 17.6398 c 63.51379,0 114.64994,43.4254 114.64994,93.25814 v 166.06467 c 0,36.2474 -27.5025,66.81614 -66.69476,81.07407 54.69829,-17.48086 93.15007,-59.39287 93.15007,-108.75267 v -131.4673 c 0,-65.16613 -66.86561,-122.29246 -149.92514,-122.29246 z"
+ style="opacity:0.65;fill:url(#linearGradient4983);fill-rule:evenodd;stroke-width:3.01772833"
+ id="path3082" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,187.88807 c -85.48249,0 -154.30223,54.6531 -154.30223,122.5412 v 136.95677 c 0,51.41616 39.57782,95.07978 95.87093,113.29631 -40.33581,-14.85349 -68.6401,-46.70438 -68.6401,-84.46164 v -173.001 c 0,-51.91359 52.62584,-101.87944 117.99736,-101.87944 l 18.15245,-3.4e-4 c 65.36715,0 117.99298,49.96583 117.99298,101.87944 v 173.00099 c 0,37.75727 -28.3043,69.60817 -68.64012,84.46163 56.29315,-18.21653 95.87096,-61.88014 95.87096,-113.2963 V 310.4289 c 0,-67.8881 -68.81976,-122.54118 -154.30223,-122.54118 z"
+ style="opacity:0.5;fill:url(#linearGradient4980);fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2931" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,199.88821 c -83.05952,0 -149.92515,45.52381 -149.92515,111.93356 v 133.97558 c 0,50.29514 38.45179,92.99187 93.15445,110.82307 C 414.96119,542.08221 387.4587,510.93897 387.4587,474.00145 V 304.76987 c 0,-50.78207 51.13176,-91.72024 114.64554,-91.72024 h 17.6398 c 63.51379,0 114.64994,40.93817 114.64994,91.72024 v 169.23158 c 0,36.93752 -27.5025,68.08076 -66.69476,82.61897 54.69829,-17.8312 93.15007,-60.52793 93.15007,-110.82307 V 311.82177 c 0,-66.40975 -66.86561,-111.93356 -149.92514,-111.93356 z"
+ style="opacity:0.5;fill:url(#linearGradient4971);fill-rule:evenodd;stroke-width:3.01772833"
+ id="rect2875" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,192.03933 c -117.25692,0 -144.31249,81.67662 -144.31249,118.61765 0,110.81954 9.01707,110.81954 9.01707,0 0,-81.27025 72.13215,-113.34537 135.29542,-113.34537 63.16326,7e-4 135.2954,35.39264 135.2954,113.34537 0,110.81954 9.02146,110.81954 9.02146,0 0,-36.94103 -27.05996,-118.61765 -144.31686,-118.61765 z"
+ style="opacity:0.8;fill:url(#linearGradient4968);fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2933" />
+ <rect
+ x="323.9566"
+ y="357.59714"
+ width="371.55212"
+ height="253.34859"
+ ry="1.9118049"
+ rx="2.3074045"
+ style="fill:url(#linearGradient3151);fill-rule:evenodd;stroke-width:3.01772833"
+ id="rect1941" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 331.08188,610.95331 c 8.3528,-8.10605 83.73569,-14.49145 178.38542,-14.49145 93.35299,0 167.50834,6.30892 177.61479,14.26203 -0.18461,0 -0.13845,0.22942 -0.27689,0.22942 z"
+ style="opacity:0.5;fill:#000000;fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2768" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,197.15513 c -114.64992,0 -141.10525,67.66709 -141.10525,97.06436 0,88.18571 8.81991,88.18571 8.81991,0 0,-64.66912 70.52414,-92.86779 132.28534,-92.86779 61.76122,6.1e-4 132.28972,30.83556 132.28972,92.86779 0,88.18571 8.81553,88.18571 8.81553,0 0,-29.39727 -26.45532,-97.06436 -141.10525,-97.06436 z"
+ style="opacity:1;fill:url(#linearGradient4965);fill-opacity:1;fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2885" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 345.56746,357.59712 c 7.93046,8.11368 79.50178,14.47618 169.36574,14.47618 88.63275,0 159.02983,-6.29748 168.62525,-14.2506 -0.13145,-0.0122 -0.13145,-0.22558 -0.26289,-0.22558 z"
+ style="opacity:0.5;fill:#ffffff;fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2727" />
+ </g>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer1"
+ inkscape:label="Layer 3"
+ sodipodi:insensitive="true">
+ <g
+ id="g5307">
+ <path
+ inkscape:connector-curvature="0"
+ id="path5137"
+ d="M 8.8349637,9.3749997 6.0974634,9.4124997 6.1349634,14.1 8.7974637,14.0625"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#00000c;stroke-width:0.93749994;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5139"
+ d="M 8.9474637,11.6438 6.3974634,11.6813"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#0000ff;fill-opacity:1;fill-rule:evenodd;stroke:#00000c;stroke-width:0.93749994;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ </g>
+ </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-mac/encrypt-inactive-18.svg
@@ -0,0 +1,330 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ viewBox="0 0 16.874999 16.875"
+ id="svg3423"
+ version="1.1"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06"
+ sodipodi:docname="encrypt-inactive.svg"
+ inkscape:export-filename="/Users/pbr/enigmail/Logo/Enigmail Icon V3 16x16.png"
+ inkscape:export-xdpi="2.6199999"
+ inkscape:export-ydpi="2.6199999">
+ <defs
+ id="defs3425">
+ <linearGradient
+ id="linearGradient4972"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#d5d211;stop-opacity:1;"
+ offset="0"
+ id="stop4974" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4964"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4966" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4983"
+ y2="11.711"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="35.995998"
+ gradientTransform="matrix(7.1505593,0,0,8.534094,228.71232,184.93067)"
+ y1="1.6246001"
+ x1="35.995998"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient2780">
+ <stop
+ id="stop2782"
+ style="stop-color:#ffffff"
+ offset="0" />
+ <stop
+ id="stop2784"
+ style="stop-color:#ffffff;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4980"
+ y2="6.0918002"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="39.723999"
+ gradientTransform="matrix(7.1746573,0,0,8.6675651,228.71232,177.84541)"
+ y1="11.17"
+ x1="38.362999"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient2834">
+ <stop
+ id="stop2836"
+ style="stop-color:#e59a00"
+ offset="0" />
+ <stop
+ id="stop2838"
+ style="stop-color:#faff7d;stop-opacity:.18367"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4971"
+ y2="7.5862999"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="38.782001"
+ gradientTransform="matrix(7.1505593,0,0,8.6966416,228.71232,177.84541)"
+ y1="9.5746002"
+ x1="38.782001"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4968"
+ y2="-13.17"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="33.183998"
+ gradientTransform="matrix(9.6839253,0,0,6.8812942,222.29523,170.28389)"
+ y1="24.385"
+ x1="33.183998"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4965"
+ y2="-13.17"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="33.183998"
+ gradientTransform="matrix(9.4687954,0,0,5.4755733,228.71232,182.51659)"
+ y1="-4.6247249"
+ x1="33.003456"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3057">
+ <stop
+ id="stop3059"
+ style="stop-color:#282828"
+ offset="0" />
+ <stop
+ id="stop3061"
+ style="stop-color:#282828;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3151"
+ y2="52.609001"
+ spreadMethod="reflect"
+ gradientUnits="userSpaceOnUse"
+ x2="44.723999"
+ gradientTransform="matrix(9.9301464,0,0,6.770848,212.4891,154.91826)"
+ y1="52.609001"
+ x1="29.614"
+ inkscape:collect="always">
+ <stop
+ id="stop2703"
+ style="stop-color:#db9300"
+ offset="0" />
+ <stop
+ id="stop2770"
+ style="stop-color:#fac700"
+ offset=".75" />
+ <stop
+ id="stop2705"
+ style="stop-color:#fff363"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2713">
+ <stop
+ id="stop2715"
+ style="stop-color:#000000"
+ offset="0" />
+ <stop
+ id="stop2717"
+ style="stop-color:#ffffff"
+ offset="1" />
+ </linearGradient>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Greyscale"
+ id="filter5183">
+ <feColorMatrix
+ values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"
+ id="feColorMatrix5181"
+ result="fbSourceGraphic" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix5185" />
+ <feColorMatrix
+ id="feColorMatrix5187"
+ values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"
+ in="fbSourceGraphic"
+ result="fbSourceGraphic" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix5299" />
+ <feColorMatrix
+ id="feColorMatrix5301"
+ values="0.21 0.72 0.072 -0.3 0 0.21 0.72 0.072 -0.3 0 0.21 0.72 0.072 -0.3 0 0 0 0 1 0 "
+ in="fbSourceGraphic" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="25"
+ inkscape:cx="5.0307505"
+ inkscape:cy="6.0232448"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer3"
+ showgrid="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1239"
+ inkscape:window-height="755"
+ inkscape:window-x="45"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ units="px">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4919"
+ originx="-284.74604"
+ originy="-71.484602" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata3428">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="Layer 2"
+ style="display:inline"
+ transform="translate(-284.74607,-655.73485)"
+ sodipodi:insensitive="true">
+ <g
+ transform="matrix(0.03532479,0,0,0.03960741,274.23987,648.41157)"
+ inkscape:label="Layer 2"
+ id="layer2-4"
+ style="filter:url(#filter5183)">
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,184.8967 c -87.94487,0 -158.74505,56.60787 -158.74505,126.92368 v 141.02397 h 44.09513 V 311.82038 c 0,-50.7856 51.13613,-91.66768 114.64992,-91.66768 63.51378,0 114.64993,40.88208 114.64993,91.66768 V 452.84435 H 669.6692 V 311.82038 c 0,-70.31581 -70.80018,-126.92368 -158.74505,-126.92368 z"
+ style="fill:#282828;fill-rule:evenodd;stroke-width:3.01772833"
+ id="rect2723" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,194.10762 c -83.05952,0 -149.92515,57.12633 -149.92515,122.29246 v 131.4673 c 0,49.3598 38.45179,91.27181 93.15445,108.75267 C 414.96119,542.36212 387.4587,511.79338 387.4587,475.54598 V 309.48131 c 0,-49.83274 51.13176,-93.25814 114.64554,-93.25814 h 17.6398 c 63.51379,0 114.64994,43.4254 114.64994,93.25814 v 166.06467 c 0,36.2474 -27.5025,66.81614 -66.69476,81.07407 54.69829,-17.48086 93.15007,-59.39287 93.15007,-108.75267 v -131.4673 c 0,-65.16613 -66.86561,-122.29246 -149.92514,-122.29246 z"
+ style="opacity:0.65;fill:url(#linearGradient4983);fill-rule:evenodd;stroke-width:3.01772833"
+ id="path3082" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,187.88807 c -85.48249,0 -154.30223,54.6531 -154.30223,122.5412 v 136.95677 c 0,51.41616 39.57782,95.07978 95.87093,113.29631 -40.33581,-14.85349 -68.6401,-46.70438 -68.6401,-84.46164 v -173.001 c 0,-51.91359 52.62584,-101.87944 117.99736,-101.87944 l 18.15245,-3.4e-4 c 65.36715,0 117.99298,49.96583 117.99298,101.87944 v 173.00099 c 0,37.75727 -28.3043,69.60817 -68.64012,84.46163 56.29315,-18.21653 95.87096,-61.88014 95.87096,-113.2963 V 310.4289 c 0,-67.8881 -68.81976,-122.54118 -154.30223,-122.54118 z"
+ style="opacity:0.5;fill:url(#linearGradient4980);fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2931" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,199.88821 c -83.05952,0 -149.92515,45.52381 -149.92515,111.93356 v 133.97558 c 0,50.29514 38.45179,92.99187 93.15445,110.82307 C 414.96119,542.08221 387.4587,510.93897 387.4587,474.00145 V 304.76987 c 0,-50.78207 51.13176,-91.72024 114.64554,-91.72024 h 17.6398 c 63.51379,0 114.64994,40.93817 114.64994,91.72024 v 169.23158 c 0,36.93752 -27.5025,68.08076 -66.69476,82.61897 54.69829,-17.8312 93.15007,-60.52793 93.15007,-110.82307 V 311.82177 c 0,-66.40975 -66.86561,-111.93356 -149.92514,-111.93356 z"
+ style="opacity:0.5;fill:url(#linearGradient4971);fill-rule:evenodd;stroke-width:3.01772833"
+ id="rect2875" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,192.03933 c -117.25692,0 -144.31249,81.67662 -144.31249,118.61765 0,110.81954 9.01707,110.81954 9.01707,0 0,-81.27025 72.13215,-113.34537 135.29542,-113.34537 63.16326,7e-4 135.2954,35.39264 135.2954,113.34537 0,110.81954 9.02146,110.81954 9.02146,0 0,-36.94103 -27.05996,-118.61765 -144.31686,-118.61765 z"
+ style="opacity:0.8;fill:url(#linearGradient4968);fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2933" />
+ <rect
+ x="323.9566"
+ y="357.59714"
+ width="371.55212"
+ height="253.34859"
+ ry="1.9118049"
+ rx="2.3074045"
+ style="fill:url(#linearGradient3151);fill-rule:evenodd;stroke-width:3.01772833"
+ id="rect1941" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 331.08188,610.95331 c 8.3528,-8.10605 83.73569,-14.49145 178.38542,-14.49145 93.35299,0 167.50834,6.30892 177.61479,14.26203 -0.18461,0 -0.13845,0.22942 -0.27689,0.22942 z"
+ style="opacity:0.5;fill:#000000;fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2768" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,197.15513 c -114.64992,0 -141.10525,67.66709 -141.10525,97.06436 0,88.18571 8.81991,88.18571 8.81991,0 0,-64.66912 70.52414,-92.86779 132.28534,-92.86779 61.76122,6.1e-4 132.28972,30.83556 132.28972,92.86779 0,88.18571 8.81553,88.18571 8.81553,0 0,-29.39727 -26.45532,-97.06436 -141.10525,-97.06436 z"
+ style="opacity:1;fill:url(#linearGradient4965);fill-opacity:1;fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2885" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 345.56746,357.59712 c 7.93046,8.11368 79.50178,14.47618 169.36574,14.47618 88.63275,0 159.02983,-6.29748 168.62525,-14.2506 -0.13145,-0.0122 -0.13145,-0.22558 -0.26289,-0.22558 z"
+ style="opacity:0.5;fill:#ffffff;fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2727" />
+ </g>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer1"
+ inkscape:label="Layer 3"
+ sodipodi:insensitive="true">
+ <g
+ id="g5307">
+ <path
+ inkscape:connector-curvature="0"
+ id="path5137"
+ d="M 8.8349637,9.3749997 6.0974634,9.4124997 6.1349634,14.1 8.7974637,14.0625"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#00000c;stroke-width:0.93749994;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5139"
+ d="M 8.9474637,11.6438 6.3974634,11.6813"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#0000ff;fill-opacity:1;fill-rule:evenodd;stroke:#00000c;stroke-width:0.93749994;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ </g>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer3"
+ inkscape:label="Layer 4">
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:2.24979424;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 15.667684,15.742702 c -3.40903,-2.920537 -4.694046,-5.522759 -4.694046,-5.522759"
+ id="path4586"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:2.15561461;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 15.747607,10.026563 -5.09709,5.759107"
+ id="path5174"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ </g>
+</svg>
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ae175f67fd5e757913691a4773272cc7e0f03d10
GIT binary patch
literal 1017
zc$@+G0|xwwP)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D1CB{VK~y+Th16S2
z6jvAr@DIfcTdip=n#LHmF@=bs8XIq`4KZF5q(KD}^np;Hj2fdzQn41a*-&hV7bHkZ
zu~s2?DNptCU}EY63TnA1i-7D5aIA~E?k>yh?7%Kx|FfK8Hnj0e{+V;moc-;YZ!=5p
ztB%4XtK(SW$@YTJi=PxM*d&*UiDHo{NQ|6M8YP;m6Kx(L+8WvPe`TVb_7wHy?cKuD
zcvy(q40aSH7fK`^NkHPvIWi05jX+Ni6Xi)_Vs#WRwssb$sNP54vG-}d@U>=(fb)BH
z{qYSyJG$@(AS;nfWIj*}u8YG&cA&ol-y)gl>L^K?89|%vJoNksdmnBP-j=V0yI~t^
zY6%b2<*<AC$2R~~0$B%iL5OG%atH9Q2GJLiimqoTmt^(TZ0F$1YW6+e%-+@v;cMF|
zl&UrCx`p*SMeN_Oko6g{{eX@D&c^jRzN7{Cx0uh7RCINhF3IYz-NA;dmVIqm>}|^y
zKF3}$=s3jgySc1a=5lap5^I*&QJn4;&>Ex*@Vy{WI$ASbQqlFiY-vjO*=4N1sOLa$
zDSEt540at80p&;5o9t}lZDr%rPuM>>h5@|*uHgC#a5ct@%pX%y@2aC>g|)lvOXW>N
z9_ylvM?Cjf?>{g6$|<2Ml^nbYcuOj4AH=anH0mcB9wG_=bfOIh(S~?Qy(=Yxu2kF)
z3V@yAKwl~Ay}zK>jlvkXDU6;gJaVp(1Bq7lPn~3<2JkT8AWr-zPP}nK8zSh>s}%R6
z6C41lDJNKSR<K&TiF;~x2;)_=2tRFLBX>J%A5IM!lVcS0)d|?ZOYs))c_cBa4ax-*
zU9EWjDnU<A@laPj2VIxBw=RvH)wV)s)v5!Ia$C<pfsK7hvvi<cKri6Yuc8Gmj60FN
zUZRgAX`-pt?u)AxZ|iA1_D}3@-_60lf8&wCCRQ%4!2!nV?fN+h+B{1MfcwZLJa(pu
z;o68VxPjiTMW)H^MCv7=&E<G>JNsJFfmU-!YmY>|{M9&&4<!RB2X+n6F3b}GL^iBd
z;zcRK{IjSwsCB6v>U+$d2g}jh3J&Qin`{3@ZX*C`0IGl<GJ9#B4*i{nc@D;D$V|C)
zta`7l<*-NP@bK&Lm}~O(c;Te`fgcB8!%YhkgK->^jF_j5m0QQEuYMi>SLE&S0-6rM
n{9l-DqUM@;MJ&pMH;w2YsxCo>5wLfA00000NkvXXu0mjf3)k7T
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..265eda8ebf08c7d2ade38b731cbae96db604d38f
GIT binary patch
literal 715
zc$@*t0yO=JP)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0$@o*K~y+TrI77Q
zQ(+i~_o;9G4}&OaWJzJ=TOv^kQHU|YJ|;m>Ayj@yGDBLj#ju(&DN}qKk|05E(uaZw
zn`PyeJ9BRBIh&7d)($&Ix1G&tzn(KsK_E`o1vfn0&;7gLzBSo!x8`fPmYpV7^N#kf
z&D*Qg_Wz+sJ%$24G}b_)6IUp;)quO9YgNB<!`;4D>uE3G#mQ?Br<<tcsHX}4b%dXt
z0e5qEZgRIVKQANwVnEDy72?OERGd0Vh4EU%115yW&SB=(o~_GgOojU3&?zKCJ&-<C
zLv++o$$6TR?lOd5>5-i8X7Y=08w(rN$mZ~x3-xohI;6rjNKQROXFZj;^K_BB1mXQz
zB!V4Sj=Y4ovqDfKo5N?>SKz%@1bKcGiGZ2i_yS#=ZJ`O?2zl;4+hhP~=^ePiW2<T;
zJ8tVit=F=jM8+>erWT~5Z<(x%E|Cr@&p$%)#|ToAo!$B=Je@^v)Y!(5jPwu@?9EJe
zm#u7u9AL801WhfxpsC1nq+?FD$!qv~D}&0srs-Fn8egA`^kFd(Cfkr;(l}{D+;0VM
ztHQUTI+_w3bai<F@`4?tzj{rY4brPfOG)&zk=?WdvDqdh!-EJ6ABVrEq}AVD+~|5x
z<_nJ0!qZ#MEAvWjN~gnmrAjNb-o8>II_|KwZ)3^TfaLdnq!&Mc^p&j*G2Bl|@<`v|
zUDJ<s+tk*^Vt#;0r@Pqd21rvUn6w8gaaX4J!ScW2EyE92U`6H;wI5)&wPxN*?@y^?
xTPHA5iPhzw|CG{GzjG%zdIam0`kf;h&2Qq9)QKj11kwNi002ovPDHLkV1f_TPLBWp
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..b96f683eef8cb216a146981bf361993e9ce17008
GIT binary patch
literal 708
zc$@*m0z3VQP)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0$E8!K~y+TrI77Q
zlTjFl_o;9D9|lp9WR1egw}wP0L?Olmza~LYAyj@yGDA|CA**I6WsdnWBte4Sqz?%Z
zHp|Mby*M{_p3P&MwXtWm?b)36>pbfe1g36XaKqs~_wR!HR&0f<wLs-;+1=%6-qrcJ
zd1n=G{~xl{-CiUFCmLvU`U<7y8aQthw({@%aCPoixjTylapoFCdlMC{^)%_bj?mLH
zaNgXTpWLM@P-dl{+7b6&h4}tB6=zS;$W$#7{w{<j&S9=&U+(g`F10o=avG`N0Ae4i
zAzEvwXsf5BzZ{{LTBMfxnfxMLI&~wDY!6SjTDxejLpo$e%%+7b747Hgit`d8Z_gqb
z&|@|H0-hdCgh#fA*KnZ7bGI1M(m0a-Zg%4fRGe?2NkIo`@gCb`2$|R$Ob%<-d1Px`
zy@ypE!$A_Bx(taLkcqx#vOX#<=&7{y5UH<YNXIPf)=%K>DW2r9%^_Lo5fZU9Gua)s
zayKMDlLe<~I{2KX!_Sb3+t?<r;4@YR<atHY4<(OpOji1!ghXcAkYv&XX+y%-3&C84
zg^@a%j#%mX>N2Ed3&>#gFX>hgqbw~W(T_TIQ$6DIO-O}?;U7H#-#}@rufL?xalhOf
z7^{WbSRu&sJo;~lQKRyh%7|#a&DOq!?~Vqfz6>I>@(#pQ4nC%EJuX!e)6qTlM}<3h
zYjcDlCbjpm)!Px9J;|g4SW7svB@9>m5$B9Bs==BhAZj_pZrhuE%e~*l$8yI%R*Ci1
qZ-15C<KOuc7(a%Miht*cLh%!e#=5wz>H=N>0000<MNUMnLSTaCLPo9t
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c8e868c2c5cfca3833de5fad81b4073154fe48c0
GIT binary patch
literal 729
zc$@**0w(>5P)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0&Yn}K~y+TrI6oC
zQ(+j#ch#MLKzD;ENwTD{QfDGj3Q>qL!7h^^s1T}MB$*<a%#c+xCS?wP4M~uoU(!WE
zgw3*Y%WclBy=U_^XKn19x$SID`}Us0F-n%&`oI?s&+|SX&hw}?!qt?kb~f&4cQkBo
z``oatLMi_rlGNRr&&`k4(a88^N-b4zHs>`d?d)*1?NPhi@;QFuD)=cK6>POM;k$<5
zlhbfs-<6%*rO(ZgrJq_6^Iie}{s`qKPtfpKHRAqu1V_(e`o`{mmru7BXaj>Mk(}>=
z@SzgCt%?fvQ<QXT5PYFUa;}@nFTkZQs8>cdho_@JJ8P*yDrkXV*Mhg#Qo(tSMx7TC
zetQOqfB{RP=kRovg_V)b;Wh5f_uMIjI5&cXzk}WQJdMsY(gdf6ID40E(vP(82F`(_
zE6T{mxJ(Ds9^*a|8oLCM8j+5?X0k52NDNe*dw}G(VWb2ryY*wZI}2YaW9vg?=|K{<
zHZa+3wsHqVKa<Um)6~K<nhHHdI%a2^yoAqO9$@b^D&3E*DqRwCG9gRv7m@Hp3ldBk
zCoPEkOyDdP_%c{SQ(+rjSz3U&U<K)`l%#59RP|ReF-y`C68WfSH#HzOqeC({0RPZ2
z_<D+)eBDL$j(Zw!V7MA?b15gimj_pcYa>C-Wol1v3E^$G*xEO-=%_>TYai0lcObo*
zUw`3xRGdS45AB?Kn72hKtuN;KnRKd)t=<Y@@;H<BU^(uP!wr;XzS~-U7p}q$m0?-r
z5V7uOw>8P%Qf)QLv-Qa&_79h1W$F7Lk!ngidjcbekts?$OH`_#On2*j;&HYw00000
LNkvXXu0mjfCCyFH
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..322698b33fb416c5d3a8239f82d4f0f00a720bae
GIT binary patch
literal 675
zc$@*F0$lxxP)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0ys%TK~y+TrIO2P
zT2T~+eS!8FoOaMxD5Q=%DfAUeXPI^64bd2pAl|JNQEH2b#)MFBp!I_HE8ex>$smC^
zaNs~Y>)*3B=g85fG);e4?0xp-JO5s5$^IsdMk7}$l|Hpv{Yj-#y%LuHPXsj>4A&PI
z7szBX@caGHYPA|+`*AWFjq;O|lk03Yi$bA*L?VGorGiK#0-a9xIJn7VdO}bFmP#c=
zqfwA+8k@}~VzC(Xdi}q{dAD!q*~8(GK`t*ZnPZ#HhHyBH!C-)RJPtlh$HJ9|iNJwC
z0MqFdS65d^rBa}j<Z?Oo-EOzRaUAS+`%1X-FrA&9JvW=pn9XKrwOYvM^C*|gXfzt=
z^?DeO$H;ajIJSNMd1Q!vdwF`UIWk6H2-okI#bS~3P%fEFqTB7_=H>?dexJFd6wK#y
zIFb##{g8yhe7+0u2LfhaB9vOI)9Em`1iihz#bU8wQRsiiyN~G|kR*lw=A58btCgcX
z60};aveE1HA{Y!}xm;qi*}(R-wgVl4DfV0wl&?5fEEd_5sm&&n34A^ub%KVE_k5wY
zRM6MPFF&N9Lom5!P`BGH=Pz;8{Pgs6I*rk2guA;tI2;bJL%jIcJasyq&lJX}P6|?-
zO6Gj*922x&ucKD0vEHQKU9DCuTrSrREQ)eSu0)X3@m`KS9uL;*HA0~fYbBb~ni%ZR
zfmnM<;39M{zf_#<cDom}CT#yv<V^el8xOmI3Uprx+mDn?_6v`aJ78M+rAhz*002ov
JPDHLkV1lYwIB5U?
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..6485acab9b32cbc5d333cc9b13c2ec277f734763
GIT binary patch
literal 757
zc$@+C0t)?!P)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0*XmQK~y+TrIP(i
zQ&Ako{RjLX22oOENzschDNDp43Na@5YZ3&NLS>L-lhK-+h}9~krm@r^twupFl2OnP
zrlsX{+f}E#ck|d*Id(O-yZO@7xtF`OEhDoB9?s#se9rUzax(rVMxCW_)o(F4>o)7g
z>Nb_i%l{`4I-$+x{Jm8qG<cp6OBq}Z+tl**#>D7$DkgN<96xjkyrY)z_DT}*Tt?vO
zak#E*-x!?HXJtyzPg*QaT?GH3gzzIrNwB{hVXpy!-jf)=y5ryB<AxlSulpFH{tnC!
z9|CVLBm8J3VNC@Hyi_4NYohQoVDvdP@|E?;-JGMEu~Z-yuwZ^v1rf?SPLT!ISp?sm
zK*ZOArMVYy-z*BsSJtN~<F0J?jXe-%ZHRcAX^&46ezKlKI6Z`!TeOl+3^T9MaQng!
zrS{RAgN-(|QunY>zP`4Y)_n@MaW^~He+~jMVmb7d!rBNw-9m)fc0|8g(f+Cr)g~K?
zn|?X|fQ(5MD?#_O!GSsoyG~1PhTx?z{~(F^pOe_!GYn4*pt}7@0w^jO{#$YhdN-F1
z4rvgf&@ih(*wYHmQi|#B3K9$2$?DP<n0osXpmeb4l+^4!5t`42KI&;tTd+7;i)f$=
z-k!tobmXZ$rra9moq{Q!wH#FjqmT?r2aBm?XpzEg&S!c1HClEPzB{WB{d^zG3-7_2
z3m_e$VjkyZvgX3AC5;a>$)MuKq&amWG1p0<jy77l7V{%VD6|7BVW(uS>p%h=m)Juw
zRUDL@IHkv2PZ3rG4xxd)w70F&TwIG=I+P~qdnLi+(3JjqtxBveeM_;pCU5^yVzT%H
n(wP1me70h&WqEsp%E<T$0KYgZTu2Fp00000NkvXXu0mjficwgB
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..156d9846dd2550d3c98306fbe8ed23d92fa9a725
GIT binary patch
literal 702
zc$@*g0zv(WP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00001b5ch_0Itp)
z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iXP)
z6CVfuPrMue00KTqL_t(I%Z-vjOB+ELhTr*iW|q2cDZ3&ojW{3yF-tZC4}u`}*0Z<%
z0Z$Upb14au+(Z!k3ks4`bJ2?%3tsXI>Y-qho1(;QFiw<APX<dAeXhg%d@u8T3~x{@
z7S&)d_|6!!0UVY}rP}j%>#eX5q8>%jm)Y6bED?Rn=kuu7>!)uD%H^`+oYxl@7iS#D
zfz}$SR0_6juVk}X#BqH3s$gqt%O#@5($dll=Ny-pmoUcQ_VyM^DM%?-qA0@2$;l~y
zNnlyl*B}UHeBa0U`8k9T5JI5YY~u0p5t&Q|L{zL)Ds6g6Ktv!SaLys6gl*ffZ5v@2
z!f_n*`+ekcIk>L-<HZJ?^WRrjS73}`YHAAIZg<$i^E^l?A*ICF*x08L!S?pH0`N1F
z$>8qp4nYv$=H><u4-dmO<KyG#bUJ9a+rQZJ@XpSTGRDMV7^;bh2@D1UNGaj_KD5>V
z0M~VqBngg=j#^slW$SsEh~g-Ul;?TST0;ndMx%jdvx)ind3c_OBuQ|5eB8RfzhB<i
z*tlQ-u)Djf0K~amPC1T)Znulc$w{1@ouSw36^$`H##qsH-498U{58g`6bgk40APE2
zd&(FS=ks|brNs61HBzY*eBVd4TE+VM`qPk)mSug`TL0PH-0TlOoG}(JEG#Hv3|g%g
z(&;pUAQ-Omi9(^!0ni!Q7!j$txjEEoHH2Y^>FH@492|@YUV{@6gb-L=UH$Kn*4EY-
k-T@~f?C<Z7T;hBA2R%v0bjV_-4gdfE07*qoM6N<$f<jb3DF6Tf
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..b5e907a99f19128410cf0328380b3eb8cbd4e3ba
GIT binary patch
literal 819
zc$@(<1I+x1P)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0?0{3K~y+TrIKAt
zl3^6bPu<i_-PO8j^R|mFx@xP7yy+&_mFuc?*Hv4$ZfzorR5U~<_(8-JrZz<!Fc)7b
z2~PY(nI8$X0X0#OSuukZ`10?0USDf&T8sYM*?G@--siV-&U5H*lFQ{)_4W0}hK7a#
znM`(HO8=iYw5h2nX}8<q^?K3Q*9V0{A(xITlS-wkYHn^$PEJn3@At#$bRrlG!eX&N
zsZ>@5Z*6V8z@Z#CJv|Mp)e2rU_lt{*7#kZyV`JmL!&$Wtc-!mi>mtPAaEKLawOR}h
z4`Xd@4K|jyJb$PXTF6{h(lrpBk*=RE4mO!gh(sb7A0LO??FJu-&*u~4P$-0CEP`Nr
zO}_M+K9pOiuKbkFN!L%8Mx(i|R;v+>MzOTCgsG`1%*@PSVPOH`a2W45Hn1DAW4Grj
z_TP|j&`qJToNoQG>W+?%Dh5rsTrRAvtYB+v3#+TEVwHRZu~-b-pW^sDQj5$emEY+I
zSt|)bI~gIT^AhH`j-Y(CmY0{sYB@BON+BMPi<Zr1u^k*j)^w$~;~?C*K}a)yFhpSn
zu9i^0#i5;@oe4fi4xOEy6}`b=fZ1$DCX+!vpGPXXj7ay*Z*UTJm{oHy^E?*1$-qW(
zSwbE{*^4Ivfq>XDU$f24O&E<vz6o+>bvm7{VCwIOUHdrmedO#m0))Ju@Rp6Dz&wwO
zqx7--itdWD?(OZZVwVzp`FZOej|Us<ank8Dy1Kg9KZj!7T%2fobi1fOtKD~zfLX$q
z3Br?F!dK>boO9EG@RBs;XLN};bbfvwb8~aB*=*wO?(OY~*3;AT3(QrPQBARflJFi@
z_`^ro9AK3)&trH9B`qb`IPQ^NuSYJI!{FecxJvw+=Z;KbiV_s0E-5KmYNL2bbAs_a
xI~U~ig81VU3Wa0Fa}uj^@drA|q~i)j^b--WOCfKLMJWIP002ovPDHLkV1obUep3Jd
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1e432f57d1d2a4afc2a7ea1789e12cebe3a58744
GIT binary patch
literal 615
zc$@)e0+{`YP)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0sKisK~y+TrISf(
zno$&nU3J&~2Dc65r*zrh(Op+ghyx-b#wigMr7lDi6+w-lbwJ~+I7MCLlR97!7cO*H
z-rjTjStz85+7}L2?>*<?OrPW}DV0j8OeS+H6pC+hx%`uO{eL2;R;!)GVliYg8F;;3
zs8lMY_<Wi4dc9Pq)6KHkEDD7JlF1}0l?p<k5Y%e*WpIPR@SdOqER{+Khr^((>1;F_
zh(sdLXf)@;S+<|4?7?8bA@O*eXMA&WgJ3X-e!q`sGz!+HFJj1PBCyZr!+1PKB9TBk
zod&fem&@`0tyT+yAi!$1ZpDz(WHOnqjYcCTlL?y5Ci3|_%H=ZZ^*XxUE=HpfmdhnP
z9?wrP<ZYSFW+{UTsZ<J`P6vy{0=-_3XGtxXPN!I{R=gUPz;`k1bseF!TJ3h5XG>7Z
z?&;}?+h()jr^Dg+LH=UszeP?Ki$$P567=EWfuAmy3x2;J&(F`;@AsI`=eWDOI~~d<
zE)<JJUNWuO<KrXTZZ~a$lG#@_r~?g@`Oc2e?$s4uG7fb*ol+Jg(DGC1_xJZ04u@E;
z*Ra`aznz#^H6QGD`$zUQz7wxr2NSebtD#!0^4+A}-EOzsSRem@&(`hjEq1#d0)YTu
zCHhTwM-$!77Y*ldIQ(UFCq7>;8qiB7K3^z_<QFDc)&Ab*384T0002ovPDHLkV1k?-
B72*H@
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..8125f28c3edd235256dfd2c9d164950398d77b0a
GIT binary patch
literal 604
zc$@)T0;BzjP)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0r5#hK~y+TrIWd;
zT2T~+t<ojWV4EV3a?3nNx>Qbx10o{M11d@^L==?^PM~qXdBj-@kHi5*EG)DOf6m&R
z8we!#s`+8Db?>!4)^tQ~NiLU*B@&58CX;=WN~KT2_5X>WYPEV5kH?YCX5sVspin5}
z!t-O&YPDjGMzhN0awryyNG6l0R;vhy!%!-fkHK|1-Ft!(uv{)95{ZDarnA**AsUTB
zrBdAw=d=AxWsgQ921z6mEMv3Tj8G_q;c$ppEC#+!Uxbj`L}0()kJ)U7=jUgn(`isk
z^7%ab-|2L~aU3ib%Si~iO$LMElU}dKd_G6J-A18MK&4Vav)M$y-^XM!!FIca*X#W$
zguE@I(J1DjTq>19uh+w7v%z36U|CWN7K;UTyB({BPvE-{_PSo6v|8P6mt{*(%5J$_
zGCLlR?6lkMKgeGQ{kO=;WHNEoM}pRBHFmn)ZUh1W91aIuE*GrVYq(sl+oAl#xl*ab
zN~SfNPN(pAJhTaN-oEmKy3#;--_=b}hr=P}gE(4#Dm|0QU_2gUzu&`ZwL)+c@zs2=
z*=!&AukoF5^|}b!Xf#l-*V%5;?w(F3W_%xigYTBp>BRYbMlcv;t3<!)?!^SR`$faK
qUax-`-3iZ+iw5*J6P_QGNc0P14ZK<qepIOd0000<MNUMnLSTX@>k(K0
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..9c8de22de1029785189e624643a4778980601516
GIT binary patch
literal 640
zc$@)%0)PF9P)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0u@O_K~y+TrISf(
zT2U0oU3Hg!2De@G8{{i=*?qr3SLw=$;DCsT8a0Xrr7lDi6~PI#4#YVs4vD((NgPnb
zg$t#d_TP6d&(H>IQu~L))qBtRai%AFNpiVdERjf@GMVhFR4V--T>qa4s#2-O;cytK
zR0=mYH&7@Pa^d+jX*3$KTCE<Z(`n>#ImBWy6pKap{eE0tUY-WmYPDwsC19aYKp+qR
zWld+TRzoltgi@*ecQ~KzM=HD9?J`It5@8veOeXkzK6E-AghC<kZTc*PyiNpmyWQyb
z`-n!PNF)-VmSi#+_W$ng4jjk9Y&Nfjkk?7C*T2{4bQlZ<sMqVrX0y1xy+yTJMXS}q
z{rx>=vl(12*AF4&Wf=?xF%RY9@i>~zCZ^LV+U+*Wl3Fkv4l$q4Sv7nD--NK^+Cyoz
z8jS|amY|f~Xf$HBTCLb=wOSvO=1=FMi=Stri|=4|NCY(+jU4rnprulYoeqZs9*+mh
z<r3TN7L&;YcDwzN{Dn}_U%}+WPn^r=^Q>fAvxkQVIGs+~1UYYC_(APxpuF$+3AzV9
zH<Ha}6Z1hFEkBi>OeWFm^{`khaCLR{lm%bSTZ_f=j{h3p2v^6!1g%smD3{A@H)(g*
z>oqgJkB9sAT)zYNy1u@~X0w6U>t(A%zv*sog4=N&0@HAIyWJl~cf#}Oq5&N;;rT>~
aM85zx@$O}Jmf1%D0000<MNUMnLSTaKq#zpr
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..95df0702ae44ca263e133d74e03d71d4a2efc6ca
GIT binary patch
literal 730
zc$@*+0ww*4P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00001b5ch_0Itp)
z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iXP)
z6CN=r?ij=X00LS`L_t(I%Z-yuNK|nY#((F3Z_YT*ORdnEbS{NiQPCUI63K*;9z<Y-
z60`^`f+%QFM7fA4DuSq0x{2Df>#>Q*F@uPp=!ue*I+mr7=p@bLT<^a{p^_7NmWT8E
zIGpo+!W5Ns=UT?7a{_RU8tRri`TZ`_t)x5GO6Dx-vTGW(U4L7TlpfQGtl6g~&Zz&v
ziR}3no=}Uq=Td|`6Z-F?%2#1-JkyZy6^@A*Y2nO(PiFX^ne~kkMq@ZHuYsL{ul-1A
z8HRUF#qr*EoKAj!lzszu-X^tZR|G4F^P&mK3?YHJIG;M9`2kY70<-!;Ln6EQkeQAb
z!vHe~9uIiuAl@)|XJS+>gMlIRk3poi4E22Z$+QBPM!VB~17pT+)Vg|zy#enm@aMt;
zKN9eR--Mi^;3R=W{z?mR-7%|=qQ7(^YnP(^ei;6V_<RubL17N`j-tCCw#r0!q;S3E
zI8M~qamtcN0PGYJm<>w{U|<*=4VDkSY3w8WdgMo}+?WV!t)s^7bC!fXFxZFqLfDOG
z(NEe?HC3=M3wj;w!v}k0=u>${>~WX;flDlHxTC7>q9wi*d~QdUmtkKy4+HNTfCSJW
z`KvQ^->Ys(#VaymPrCr*SMhcWJ03OmUbRsD@U{i1tb@fNQng!fe`pqfM7YQ=S@XgU
ztsgz_e~ygZTkGMdxxdLG-huPvJgVk0lobB;>G+8<zjVZ*BR=V2C}qjc0-OU|QQK}J
zRYjyWSN|1IQ|84Kae-H%uD>2PlDKfS-IM7A&csyM8+QEDRnt!FZyv$LNUg?PtN;K2
M07*qoM6N<$f;(tYTmS$7
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..9da65a84e2165c1247fe61c84d346cd7b74bc282
GIT binary patch
literal 749
zc$@+40uud+P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00001b5ch_0Itp)
z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iXP)
z6CMZWRE=E#00M1EL_t(I%Z*b_NK|1IJ@@;WH{;BEGozr%AoJ8DMWiV!haekVq(}u(
zNl+jXE!wt@nu6$OUAS};1VTiAn;=Ic$O?)U>5#@){-utn<L}J8d0&eXwG=(ud(XLt
zd(S0ogZ^lxsVuq*f<~k|t8T`hudmZ~WG0>6`Qf^t&~+>h4q|@bTeRwW-;LECz9so~
z$Yerx7acnu)CvU{jl*451tyKi#p)~`bWQebDG<NAM`z^tP}F!PD7<M{sm}mxWM-0Z
z?+$@UJ#wi!gZr;1dc>yV$<^cvlm(r;ijf}c1PKoq&qJzj2-$^?@YOT|kkR-(Hp@2S
za}E%35IF&<gK+~SyeK$)2vYaK&dnqLKr<ZP{A-&!U=AK%NhV<DoRHoy?DPnTc_4%b
zQZEMy10;2Dx7YWdK!1DKR5|;ZzxD{AMj?%Zu$FWvYY8NDkTgJA2{N-&$jpwt*>J{0
zd)UkYG5@{>GcQmCr7V#0fjl)Jl?0%`oF%a4k{Ew=XP8z}^?W_qmK5{XHk+>E0w`+^
zuzi?(_ZZ7RKfzso0z$7qHZ_G`UAKm5ajw4fa_<lUK%ygTD#c^|&`DD(aKKKFK^S{6
z(ftVa@=O#8%ZgZ3cnV$C!qg8!Hd5NwI|Klf=!lqLxtKq6+SGhmSPOAT|50#HB}SfH
zL22t-0)T;sb%wOHz@l^ddv(j#%&K!iG8glO&YRLbjm%6hw8B#m-r7HL;A<<YxbQv&
zAocIk1q3ra<xQCX(gH2e2H|hO$kW!pRkq!)K@evk?uN1d<ysIqL-1^jumcF<45Kg3
fZA>y}*eQMkcO%GeB_#9*00000NkvXXu0mjfvp+-x
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..bfb4f372c22f0a0ea580319fc2b02cfc52bb5ed8
GIT binary patch
literal 797
zc$@(p1LFLNP)<h;3K|Lk000e1NJLTq000yK000mO1^@s678qX}00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0<uX&K~y+Tjg)Oj
zlTj4M&xRvin1tjaKWLOCld>?V{30faVv7bPL_*&JsRe0;)WlU}^&u$eRm#MSz?2fS
zzy#Akk{~L4e_LA#x-rvd+cRJ0PXFs3tFa0G;m<wyKF|5>oO}0NTCFCV^&72kQjNbV
zH^noN23Co6jOqZ9&PSBDKxABcc}W)nu}T!C-s;2@TVvLY^sSC<b%h>VQQR>=VgwA1
zL!1lPoq3{CC1qs^*l<(6YrM_KqwlNu^(g~Sn)b?5TQz#Xw1A~A5?u$Z5zs?QT4XK5
zWczKF1}?MhX%0I~g)AT5$M!wya=^CuiHhMi0Bh!mb}Fgi`X46SZ?QaTVX3Kzr?V1R
z(rFgGTB-!_I6w`k>?@#Um?tRdp$dc9uCcejjO7PMSW1pr@TurGs(A<43wQ{?&RHS@
zwjf$T{8L1j)y&?mVs;vI@~oOBB%*l$#sRfrbO2b5I87Xqf(a@j>_<6!21?m(PMPsV
zQ>&Lq@(?Y~01B`S&=}f1hjuq8n4ngKja}r?hdZX+I`t))s0T1NK(lT@#fS<!P+@LJ
zyWE}?^|LvDI1+5QW|wOsQ*#hW_e7gw&)_it=kRBq!rCKvTS&Vj^{E*{E!lo&pCu3i
ztKIGDJ)=znEDx=<0@MpXLyPs8PaqzIPfruAS5QF>)+V2Ib>C%c!vS`DHwTtsU#`Zc
zU~l4)R03!h#^)H13JAt-fLri98(yQJf@-MP-s$as%gzrsxaX$c-&d7BB4FYfUe+V|
zJ|M3EIDvT*_A(P|kMV_AFy9<@y2kx$JulBEef(@$XZlp8&1kQnDCH3$u>grr04ouD
zC91(mCt`jQaRI#I9S~8d%mnBaBxa%#Ev!Nu1M6W?-QOQyl>jp!F$Wgc*7B955^!tS
bmqGLgz~{6BRIYwu00000NkvXXu0mjfmg!hD
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..7099d0039b48d34e8799920e4e737e7f85f11042
GIT binary patch
literal 405
zc$@*10c!q<P)<h;3K|Lk000e1NJLTq000yK000mO1^@s678qX}00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0V+vEK~y+TV;~m1
z+@d9Me}hKg-Bn8bP$2@K^dB#jg#oV4lU9AW*8bzC%gg@X-xB}%;v`91s1)e{WZ3PE
zo^PM-3H*QO(1QQ7nv?(EUl#i5Ot~PDE=2+`!@fQ0{r~A^&Hwvry#C+aJN5tD!+rmc
z7qR(4l@SM!4g2<}^Z&Pp&Hq1MFZh3NrOp4d^|GH%W-&-Wl@bQn4Ey?^_WzfAW&dw4
zGJJckiB}b>lrR_}hW+1f&+>SS=WVJO1_F0hX&B$y>iFmNwT6Gh7<NHR>pwOOm1bbL
zxkw@J?a7J2bmsK`)uoz$gj|NrFc5}HGcc^FS3mUi`nLbC548Wk(xLU|+ARG$BzqGX
z1X^-`SzaQ4_;dqb?uiO6<NfsvEKsRo2N)OtF{~?M>m3;700000NkvXXu0mjfr4+j#
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c325b0fa2743e6f6ac1ed8a98cfe6c94d92bb94f
GIT binary patch
literal 379
zc$@)y0fhdEP)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0T4+<K~y+TV;}{*
z+@d9Me}hKg-Bn8bP$9~})p^pY57*j%{B(KQ|NC3wKVO_AX$zGiAAk(Kz0vdS(>;Oz
z?;Kk2e^zty|NF~AKb<KTB;VyQL%%)h{r~A^&Hwvry#C+aJN5tD!+rmc7qR(4m5~OJ
z4gL10^Z&Pp&Hq1MFZh3NrOp4d^|GH%W-&-Wl@brI8T$1>?f)<L%KqP8Wcc=66R#>%
zDe+*08~T60J<H=Qp0}xLC<xqHrD1$!tJa^>+v@*O%}@}yxkw@J?a7J2bn5i~)TWw$
zR526;*3_#X`g(oa|JMiF|6i%s`g3iT{vC=91%W_I?k~$r<PV>2;LANx!DS2;9x;G{
Z0RZ6|4ppka6R!XO002ovPDHLkV1n#Au?_$L
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a4084ea806d97603b9c51167d2500692a3a48167
GIT binary patch
literal 412
zc$@*80b~A&P)<h;3K|Lk000e1NJLTq000yK000mO1^@s678qX}00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0WnELK~y+TV;~m1
z+@d9Me}hKg-Bn8bP$2@K^dB#jg#oV4lU9AW*8bzC%gg@X-xB}%;v`91s1)e{WZ3PE
zo^PM-3H*QO(1QQ7nv?(EUl#i5Ot~PDE=2+`!@fQ0{r~A^&Hwvry#C+aJN5tD!+rmc
z7qR(4l@SM!4g2<}^Z&Pp&Hq1MFZh3NrOp4d^|GH%W-&-Wl@bQn4Ey?^_WzfAW&dw4
zGJJckiB}b>lrR`ZhJlh<>5u<lj1K<qw`Y00#q&1FF2iNuomCpfx3)U|d3~+nA5n(E
z3_`~+aC4DD-rJKCf$7ZY|Eo(i|A=-OI);HY_3DSdUf=fr^?~;PS30!*T$`nTho}fZ
zHwXp-ExEreFOffdx`8kEL<N`e{(1%$s1$yH%fLYl7#IL`sY9-g?o1y50000<MNUMn
GLSTX~$-e>s
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..682a501758e923dd75a95214c1f9c3fc33366d21
GIT binary patch
literal 466
zc$@*!0WJQCP)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0cS}>K~y+TV;}`g
znKDIU^5n^Z9UUF~P$9}eQ&W@btXZ=@o;!E$|NQy$KiAgQ+Crts2OvWiE?oF_*|KH-
z_wV2TzoDT4Wca7@@^bQB4m0%3nKS<n9Xj-X&YU^_ckS8*wjet@+YhRYG=OaA>C>nG
zpFDZ;|K7cO|4*AX?SFlJ{im#~ED5Mm;sG{8j~zSq|M20%|A7(swzRZV6{?hYK#ZY|
z?Q86uI@Z-Ywyxa|rh$B@cI<!{LtzFwcdz>o!!Qe=#-IXXU5acd2p}7d;c{RsvjCIO
zZ9-uRauv)Vbb~<{!_^ZfPBd<8Z2Yro)vAB^A__eS&<zI3LzO}RFq`Iq(kU=^{a><V
z$v<p{f&jXUojZ`&K#bwy!otEs*REarzkBy?P#XQy)6;VY-B1u9IgBkVEWTu9WE?Ij
zDap;t%QFTV&H|Of03cVP2MLIe;aYM4$Yn4Kz%;r`X#*G-08`7cwB1dl@Bjb+07*qo
IM6N<$f>jaAzW@LL
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e6e3966707b9196043d312f42a32f12325ed8410
GIT binary patch
literal 476
zc$@*;0VDp2P)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0dYx0K~y+TV;}{*
z+@d9Me}hKg-Bn8bP$9~})p^pY57*j%{B(KQ|NC3wKVO_AX$zGiAAk(Kz0vdS(>;Oz
z?;Kk2e^zty|NF~AKb<KTB;VyQL%%)h{r~A^&Hwvry#C+aJN5tD!+rmc7qR(4m5~OJ
z4gL10^Z&Pp&Hq1MFZh3NrOp4d^|GH%W-&-Wl@brI8T$1>?f)<L%KqP8Wcc=66R#>%
zDe-_9Lmk`K*g18qt9NW&yB|yg`B3fH0WpTc40P^Z_aBB~7C?<b1;n}(*-#KbHXOs{
z|M%OoJl^7Yn^2g7Tm>@--Cz*LaP^&48pc<)YW+FAt^Oarh(Zqnbb~?iP^A!XbCE*c
z+mjQ4>D1}}sZBNiuo(&h=q`5dKw<+ihKtwKs~`G$ecS)n2ipH%sn_~*ZI=EWbVEUa
z<S-7j<o>d}ME>yU2EN=A6<o$pVXOe;D)b-$@iAOW4gk3fW&xN+cPVWE0|NlU?ys4K
SFl4U)0000<MNUMnLSTZEh}?Ss
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..12e5adbf458f2485407b319fb3157469a5756a6d
GIT binary patch
literal 434
zc%17D@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|*pj^6T^Rm@
z;DWu&Cj&(|3p^r=f!eQwFr$;k><XYDdx@v7EBj404q;)&-7~{ffu<OFx;TbdoIZPL
zqu1d8kz*hA_vS85$+nJYYT<r0scAvCPz^)*{3ZGq)_Q+)ic@UvS77NW?0KtjkVlB;
zj!x^POVeiGkydYCaN_K<ndd(J=5JrT@ZgSh)n_dCYj!;<G@7}Falz8A^vyRV6;-z7
z{yu2XbN*#X6*t2yEi<0SOE16t;=Wk0=6-wTtl3AC3Y|hWrbhmaTi>qYX&~2cD8bV=
z?R4sjm5)}fQdoMVY`3e-@e`kG4og_)n;vxHDA^s$p%}LN?z>Z^vCBXLE`eLTE`PaI
zIOltO{|%P^941G%HaxF<`?v0YTI98rTI+0srlpEAteCPX=;&64=XaJ(nWWLRWs!!K
zC=bKi_pOFy_A-2qnyvyYlLS6ZeE#!p{0=dW!yi5sSlGPW^{(*T{L`Ci=AM79`tNTI
ZWBqA{&9_6X76Aj4!PC{xWt~$(6974Hy4C;y
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..cdf915d5e4df91e68118a7614585b9da6be2ec63
GIT binary patch
literal 646
zc$@)-0(t$3P)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0vkz0K~y+T)sxF>
z6Hye#KcS#IS3cH41;LGU6BM;BbfF-Ke}LOyXkZ{QbumnmRn(*<CJh)<LTJn)x^dwv
zF$NQegv6+uh*W%SDoW8dDwvsjkLSi|Nu_OUUHQS~aPGP1H{aX`HllbuzB?9+oe70P
z+wG};S1=eHP9~EJtyYWD>2yce^}}{)^HR`ECNrPQ<)~aPli%-G@I{}`w|VDF&}Oqq
zl}d$DsT36oh295TF4t*$*~ZilZ8REGuh*$mDp4YlAiu6Jw#N4y5g}w`5c>$&Vz;fY
z)u6RnjjGiu@d3_HXy^9JOYoUF%*qfh$T4iSTi4bg=(wisl>r>GrtqwD2ZG~MY2e_x
zt_MNAUhgm&*d`*F{&WkX90PskC?=3n^EPIs{syYnvD;(T3|`FL1|2;B`pD6`f#(b!
z?ScBmr#!@0Xm%j<B6f*5ySodT<1wnh991r+kmN1d*#j;q8jX$w0s$+Z&&#!-Y7b=!
zC%SojpbF^wV^EQ|;s-~ScW>goypH25_L`rmTisKBU6jpc<)2U`<wjh?Wal0PJpp}r
z2s(cj^p&H^7w*I2dW8RKhus2bnpSDI+f*zT$uJBn91g$khbl=Or@E7PM}^-Ge?9`e
zoMERNRZb5;=2g6911Iekz~OLwb2^<>kH<6ScDqLyK4h1cCH%Nz2Jy-a<Bb_vWvF?s
g3j>$!mj6b;FQ&-AtsZH$djJ3c07*qoM6N<$f_i5iBLDyZ
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2a70ddb6b0398415db87d3fb6ae856d3970fa8f9
GIT binary patch
literal 383
zc$@)$0f7FAP)<h;3K|Lk000e1NJLTq000yK000mO1^@s678qX}00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0Tf9@K~y+TV;~kx
znKDIU^5n^Z9UUF~P$BX`Q&W@btXZ=@o;!E$|Dr{UKG)XP+CrsB2Oz^1E?oF_*|KH-
z_wV2TzoDVw|NQy$Kb4o4lkQTOVQ0>q`G4rpq5pH{%=y1-*DkQZ+1c5CP-VmcWW!FM
zKK=jX$&>&0?%n%;+O%o^>+9=3Wo2baK$Q{(*bF;%?AZUqhY$Y;M$FsN(o$8ZQo>+_
z7zT_r7GP4iO%=mHVB*Ay#=wO82WY@QVhp>WrS%^hhDtLq0JBpbD4hWf1tpDtgj|Nr
zFc5}HGcXhu79P5G?b`p{yLbNwhUK50o}N1-dlMO0SXg|?$jCTcQc{wemzQS@Ot>si
dsbL2g7ytn@D+d_(rHlXo002ovPDHLkV1l}3q;LQL
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a1c3a3528f20175d88862d654c78fd0226122238
GIT binary patch
literal 369
zc$@)o0gnEOP)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0R~A#K~y+TV;}`g
znKDIU^5n^Z9UUF~P$9}eQ&W@btXZ=@o;!E$|NQy$KiAgQ+Crts2OvWiE?oF_*|KH-
z_wV2TzoDT4Wca7@@^bQB4m0%3nKS<n9Xj-X&YU^_ckS8*wjet@+YhRYG=OaA>C>nG
zpFDZ;|K7cO|4*AX?SFlJ{im#~ED5Mm;sG{8j~zSq|M20%|A7(swzRZV6{?hYFv1N5
z#xe^q3Eifup&&4E;zZ-d#>PLZR;~I+HA6uFm`(FQ=@gi|{x4aw<R4WG1%blC!b8`t
zUHiX#_ij)c{nOLabBAI>LBPVo;!8$G#^I8ZlH9z!JY%5YEKsQt0T>tnarY2#bh?ah
P00000NkvXXu0mjfW?iGm
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..b7a69a0d68f7a0457b3aa0fc2ad386bcdd226d55
GIT binary patch
literal 392
zc$@)<0eAk1P)<h;3K|Lk000e1NJLTq000yK000mO1^@s678qX}00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0Ub$1K~y+TV;~kx
znKDIU^5n^Z9UUF~P$BX`Q&W@btXZ=@o;!E$|Dr{UKG)XP+CrsB2Oz^1E?oF_*|KH-
z_wV2TzoDVw|NQy$Kb4o4lkQTOVQ0>q`G4rpq5pH{%=y1-*DkQZ+1c5CP-VmcWW!FM
zKK=jX$&>&0?%n%;+O%o^>+9=3Wo2baK$Q{(*bF;%?AZUqhY$Y;M$FsN(o$8ZQo>*u
z8CLp_m!b5>e=tS|z*u7eCWYH1y9}3s6DLkI1}5A;Km+~}Wf;sLbPNN)?34#eXFx+i
zN#h^UE<?vKP*_-a=-Rbw|99`+{T~>Xe|ma)?hq9L=mx=ng@whJjEs!KB_$=fd3kxp
mz=X>JmBJ5j890am0|Nj>F+)>CIL#~o0000<MNUMnLSTXc6RCFq
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..bb7b212615bc1382b1fe9b5ade219bd9c97eb2f3
GIT binary patch
literal 435
zc%17D@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|*pj^6T^Rm@
z;DWu&Cj&(|3p^r=f!eQwFr$;k><XYDdx@v7EBj404q*wd^ZUcnfTkFGx;TbdoK8+r
zVEm&wang_PPiLzhUZ4N_#<Rb_P8<LNmo?9>3%*%ZZ~x(bido#2zK7FqPj1hfv&}{N
zgH==P)cO9uS(N7++<!lvhv&N9$<H1J(z$!H4xK#jvL-EUp1jkUUCVfSp1+bb*{drt
z<7{%Jz0tqz=_+ByK5yptu^!%^`&-iPe%;TXa*xeT(}GNl4S=Ao>hOp6XWPsD|J9fq
zTYP;ksH{0l(!xUG-<_I&@7}HcE*8~Ze<v*g2$r}Qw=pis0<jy~56-s}7yEX8&+(a+
zbsoa2B~@kr?5uE_|4_bLNpn^p?~lKyX9e2z{XGBpze~0oWAzqQzQ3{cpZ?F^H1Gd?
zAD4eMn}v54pPlABb%I@mf*a?DTF&WDY}PA$%Dmb?pXd0#;=27W3MM?{KfHjQVeOW+
V7Eb?OWdTE!!PC{xWt~$(6986uzkdJ#
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..f7f52a30b335af47efc8ed05ee7748a9c5e05104
GIT binary patch
literal 453
zc$@*n0XqJPP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00001b5ch_0Itp)
z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01ejw01ejxLMWSf00007bV*G`2iXP)
z6A~S^gF7n#00BiwL_t(I%gxfiO9F8i$MMgd)@k@FlTdIG)5Rj99~2cDSl}2$^q({d
z{S6JR?m=yeL*!Nw2ceK5M_J+EDJp4r-VeXuriLSk@6pmTJ<IDoJUkpMOe>!V4t26I
z*3|3h;mZdA@T|C+3&#{PTk}V=mx4RlD4&M5nFG~aIOYm2+}WKzPphY)Ao4Cxa4Rn!
zPkU{wIFLyDC#KvO)vpkVC*k#lg!W_cmSe?$0Z<M4u!BxT#Goz@)ORV)TfvlSunCi(
zRP7R7US2yxle2;;Q6b-#x_zm(zLf1t0KokP{|H#crT{Ia><z{FG&7%`W5uRFcy2cr
zIm$;U8B<nl3Xsug(;3*awZtMvbd|DT0I+{AmAY_{S&e}NcJWrYAe@7~s<M>%4V!Lo
vl=6M<uXrD#?%uhf7&nSzSl&5-fqy?=S*h$cTTNKZ00000NkvXXu0mjf(4V}g
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..4e51c94d1fa0dbd2aacafe0f5c0d8bdee3f4c214
GIT binary patch
literal 505
zc$@+G0S5kwP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00001b5ch_0Itp)
z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01ejw01ejxLMWSf00007bV*G`2iXP)
z6BZDxUV@SU00DVPL_t(I%e|7lPr^VL#-HoQB5@%M;6+FWCk7KuL}-k&yOaL_J5I86
zW9d%P#J_;S(SgB8A|i&NvMGxp63b%ZM-Z;<orAU^gg{w*lUv?<e$QRr_aRuJEXztV
z#`3P~UX@CvcL0#0!j(#8OP1wFUDq!tr7wnI8~^}`3R6n&R8@_qQYkbV4P-Kz&u+K7
zsYHR#%H^l&o?EX&Rn_%Qr*pUz7Jvv2mn89(P747D!!SOI#bQ!f_H6*TZ@0t#UM`34
zwKXvsjdm@|a>yb$o6W|B5a67{_kH9G1pt6ANv{g$8vvlJ0*hbCMCSY?&?`}3-}f=K
zvQq+F2TYiQTCJu~N-yWZwr#%w@c*Re(~pJ*gTY`MfW|o&vv9xP-zR`V078JtTm#0K
zu4$TV+ct4sS4_jEY4(Cu&dw~Hf#W#Gi9`aLrlHwva#@z2hr{8KX_~!7SqTkVt=7(X
vJidv=Vl~Ftt><|U)oRrX?n%Vh|9|}eXD!-lw%t!)00000NkvXXu0mjfpfuRR
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-mac/enigmail-common.css
@@ -0,0 +1,415 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * Common Enigmail styles for all platforms
+ */
+
+
+.action-box {
+ width: 100px
+}
+
+#expandedEnigmailBox {
+ background-color: #d8e0e8;
+ color: black;
+}
+
+.enigmailHeaderName {
+ color: #888a85;
+ /* lower contrast, TB3 */
+ text-align: right;
+ background-color: transparent;
+ -moz-margin-end: 2px;
+}
+
+.enigmailHeaderNameBox {
+ width: 7.7em;
+ background-color: #d8e0e8;
+ color: black;
+}
+
+.enigmailHeaderValue {
+ min-width: 50px;
+ white-space: normal;
+ color: black !important;
+ line-height: 1.4em !important;
+
+ -moz-appearance: none !important;
+
+ padding: 0px !important;
+ margin: 0px !important;
+ -moz-margin-start: 3px !important;
+ border: none !important;
+ background-color: transparent !important;
+}
+
+.enigmailHeaderSpacer {
+ width: 2px;
+}
+
+.enigmailHeaderBoxLabelNoSignature {
+ background-color: #d8e0e8;
+ color: black;
+}
+
+.enigmailHeaderBoxLabelSignatureOk {
+ background-color: #ccffcc;
+ color: black;
+}
+
+.enigmailHeaderBoxLabelSignatureVerified {
+ background-color: #ccffff;
+ color: black;
+}
+
+.enigmailHeaderBoxLabelSignatureNotOk {
+ background-color: #FFD8FE;
+ color: black;
+}
+
+.enigmailHeaderBoxLabelSignatureUnknown {
+ background-color: #FFFFC9;
+ color: black;
+}
+
+.enigmailHeaderBoxLabelBuggyMailFormat {
+ background-color: #FFD8FE;
+ color: black;
+}
+
+.enigmailLink {
+ text-decoration: underline;
+ color: blue;
+ cursor: pointer;
+}
+
+.enigmailGroupbox {
+ border-radius: 4px;
+ border-style: groove;
+ border-width: thin;
+ padding: 3px;
+ margin: 3px;
+ border-color: lightgrey;
+}
+
+.enigmailCaptionboxNoTitle {
+ border: 2px groove threedface;
+ padding: 0.5em;
+ margin: 1em 2px;
+ border-radius: 4px;
+}
+
+.enigmailCaptionbox {
+ border: 2px groove threedface;
+ border-top: none;
+ padding: 0.5em;
+ margin: 1em 2px;
+ border-radius: 4px;
+}
+
+.enigmailCaptionbox>h1 {
+ font: 1em normal;
+ margin: -1em -0.5em 0;
+ border-radius: 4px;
+}
+
+.enigmailCaptionbox>h1>span {
+ float: left;
+ border-radius: 4px;
+}
+
+.enigmailCaptionbox>h1:before {
+ border-top: 2px groove threedface;
+ content: ' ';
+ float: left;
+ margin: 0.5em 2px 0 -1px;
+ width: 0.75em;
+ border-radius: 4px;
+}
+
+.enigmailCaptionbox>h1:after {
+ border-top: 2px groove threedface;
+ content: ' ';
+ display: block;
+ height: 1.5em;
+ left: 2px;
+ margin: 0 1px 0 0;
+ overflow: hidden;
+ position: relative;
+ top: 0.5em;
+ border-radius: 4px;
+}
+
+.enigmailStrong {
+ font-weight: bold;
+ color: red;
+}
+
+.enigmailUidInactive {
+ color: gray;
+ font-style: italic;
+}
+
+.enigMsgHdrView-flat-button {
+ min-width: 1px !important;
+ -moz-appearance: none !important;
+ color: black !important;
+ background-color: #DDDDDD !important;
+
+ border: 2px solid transparent;
+ margin-top: 2px !important;
+ margin-bottom: 2px !important;
+ padding: 0 2px;
+ border-color: ThreeDShadow;
+ border-width: 1px;
+ min-height: 1ex;
+}
+
+.enigMsgHdrView-flat-button:hover {
+ background-color: #CCCCCC !important;
+}
+
+.enigmailToolbar {
+ -moz-appearance: none;
+}
+
+.enigmailTitle {
+ font-size: larger;
+ font-weight: bold;
+}
+
+.enigmailDisabled {
+ color: #a6a6a6;
+ /* grey */
+}
+
+#messagepanebox[enigSigned="ok"] #messagepane {
+ color: red;
+}
+
+#expandedautocryptRow {
+ visibility: collapse;
+}
+
+.enigmailMessagePane {
+ margin: 6px;
+ -moz-user-focus: normal;
+ -moz-user-select: text;
+ cursor: text !important;
+ white-space: pre-wrap;
+ unicode-bidi: -moz-plaintext;
+}
+
+/***************************************************
+ * Various other styles
+ ***************************************************/
+
+.enigmailDialogTitle {
+ font-size: 120%;
+ font-weight: bold;
+ padding-bottom: 6px;
+}
+
+.enigmailDialogBody {
+ -moz-user-focus: normal;
+ -moz-user-select: text;
+ cursor: text !important;
+ white-space: pre-wrap;
+ unicode-bidi: -moz-plaintext;
+}
+
+.enigmailDialogInfoBox {
+ border: 1px solid #afafaf;
+ border-radius: 5px;
+ padding: 15px;
+ margin: 0px 10px 0px 10px;
+}
+
+.enigmailPrefsTitle {
+ font-weight: bold;
+ height: 25px;
+}
+
+.enigmailKeyImportHeader {
+ font-weight: bold;
+ color: #888;
+}
+
+.enigmailKeyImportUserId {
+ font-weight: bold;
+}
+
+.enigmailKeyImportCaption {
+ list-style-image: url("chrome://openpgp/skin/importSuccess.png");
+ max-height: 2em;
+ max-width: 2em;
+}
+
+.enigmailErrorIcon {
+ list-style-image: url("chrome://openpgp/skin/password-error.svg");
+ padding-top: 5px;
+ padding-bottom: 5px;
+ height: 2.2em;
+ width: 2.2em;
+}
+
+.enigmailKeyImportDetails {
+ color: blue;
+}
+
+.enigmailKeyImportDetails:hover {
+ text-decoration: underline;
+}
+
+treechildren::-moz-tree-cell-text(enigmailSubkeyTitle) {
+ font-weight: bold;
+}
+
+treechildren::-moz-tree-cell-text(enigmailOwnKey) {
+ font-weight: bold;
+}
+
+treechildren::-moz-tree-cell-text(enigKeyInactive) {
+ color: gray;
+ font-style: italic;
+}
+
+treechildren::-moz-tree-column(enigDontEncrypt) {
+ background-color: rgb(90%, 90%, 90%);
+}
+
+treechildren::-moz-tree-cell-text(fixedWidthFont) {
+ font-family: "Courier New", Courier, monospace;
+}
+
+.enigmailExpandViewButton {
+ width: 9px;
+ /* The image's width is 9 pixels */
+ list-style-image: url("chrome://openpgp/skin/twisty-clsd.png");
+}
+
+.enigmailCollapseViewButton {
+ width: 9px;
+ /* The image's width is 9 pixels */
+ list-style-image: url("chrome://openpgp/skin/twisty-open.png");
+
+}
+
+.enigmailWarningIcon {
+ list-style-image: url("chrome://openpgp/skin/warning-16.png");
+}
+
+treechildren::-moz-tree-image(enigSignedEncrypted) {
+ list-style-image: url("chrome://openpgp/skin/col-encrypted-signed.png");
+}
+
+treechildren::-moz-tree-image(enigSigned) {
+ list-style-image: url("chrome://openpgp/skin/enigSignOk.png");
+}
+
+treechildren::-moz-tree-image(enigEncrypted) {
+ list-style-image: url("chrome://openpgp/skin/enigEncOk.png");
+}
+
+/*
+ the following styles are available for the key trust
+ columnm in the key manager:
+ enigmail_keyValid_unknown
+ enigmail_keyValid_invalid
+ enigmail_keyValid_disabled
+ enigmail_keyValid_revoked
+ enigmail_keyValid_expired
+ enigmail_keyTrust_untrusted
+ enigmail_keyTrust_marginal
+ enigmail_keyTrust_full
+ enigmail_keyTrust_ultimate
+ enigmail_keyTrust_unknown
+
+They can be applied using:
+treechildren::-moz-tree-cell(STYLE) {}
+treechildren::-moz-tree-cell-text(STYLE) {}
+*/
+
+/******************************
+ * Rules for filter actions
+ ******************************/
+
+.ruleactiontarget[type="enigmail@enigmail.net#filterActionMoveDecrypt"] {
+ -moz-binding: url("chrome://messenger/content/searchWidgets.xml#ruleactiontarget-folder") !important;
+}
+
+.ruleactiontarget[type="enigmail@enigmail.net#filterActionCopyDecrypt"] {
+ -moz-binding: url("chrome://messenger/content/searchWidgets.xml#ruleactiontarget-folder") !important;
+}
+
+.ruleactiontarget[type="enigmail@enigmail.net#filterActionEncrypt"] {
+ -moz-binding: url("chrome://messenger/content/searchWidgets.xml#ruleactiontarget-forwardto") !important;
+}
+
+.enigmailPassphraseQuality {
+ margin: 2px 4px;
+ min-width: 128px;
+ height: 12px;
+ background-color: #C21540;
+}
+
+.enigmailPassphraseQuality[value="medium"] {
+ background-color: #5885C4;
+}
+
+.enigmailPassphraseQuality[value="high"] {
+ background-color: #64C4A1;
+}
+
+.enigmailPassphraseQuality[value="excellent"] {
+ background-color: #1A9C2A;
+}
+
+/* a spinning wheel circle */
+.enigmailWheel {
+ list-style-image: url("chrome://openpgp/skin/spinning-wheel.png");
+ max-width: 100%;
+ max-height: 100%;
+}
+
+.enigmailSpinning {
+ animation: enigmailDoRotation 1.4s infinite linear;
+ transform: translateZ(0);
+}
+
+@keyframes enigmailDoRotation {
+ 0% {
+ transform: rotate(0deg);
+ }
+
+ 100% {
+ transform: rotate(360deg);
+ }
+}
+
+/***************************************************
+ * Styles for Setup Wizard and Key Generation
+ ***************************************************/
+
+#passphraseBox #passphraseError {
+ margin-top: 3px;
+ margin-bottom: 3px;
+ color: red;
+}
+
+#passphraseBox #passphraseErrorRepeat {
+ margin-top: 3px;
+ margin-bottom: 3px;
+ color: red;
+}
+
+.enigmailOkSign {
+ content: url("chrome://openpgp/skin/ok-sign.svg");
+ padding-left: 5px;
+ padding-right: 5px;
+ height: 1.3em;
+}
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-mac/enigmail-html.css
@@ -0,0 +1,101 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * Enigmail style for HTML pages, displayed in tabs
+ */
+
+
+
+
+body {
+ color: black;
+ padding: 0;
+ background-color: white;
+ border-style: none;
+ font-family: Arial, Helvetica, sans-serif;
+ font-size: 14px;
+}
+
+h1 {
+ font-size: 2em;
+ color: #0077bb;
+}
+
+h2 {
+ font-size: 1.4em;
+ margin-top: 1.3em;
+}
+
+h3 {
+ font-size: 1.2em;
+ margin-top: 1.3em;
+}
+
+ul {
+ list-style-image: none;
+}
+
+a {
+ text-decoration: underline;
+}
+
+a:link {
+ color: blue;
+}
+
+a:visited {
+ color: #9900cc;
+}
+
+a:hover {
+ color: red;
+}
+
+.header-bar {
+ box-sizing: border-box;
+ font-size: 14px;
+ font-weight: 300;
+ line-height: 16.8px;
+ margin-left: 0px;
+ margin-right: 0px;
+ padding-left: 15px;
+ padding-right: 15px;
+ position: relative;
+ min-height: 80px;
+ background-color: #0077bb;
+}
+
+.header-icon {
+ box-sizing: border-box;
+ float: left;
+ font-size: 14px;
+ line-height: 16.8px;
+ margin-left: 0px;
+ margin-right: 10px;
+}
+
+.spacer {
+ padding-top: 40px;
+}
+
+.body {
+ max-width: 800px;
+}
+
+.logo-img {
+ max-height: 90px;
+}
+
+button {
+ font-size: 1.2em;
+ margin-top: 1.3em;
+}
+
+.hidden {
+ visibility: collapse;
+ max-height: 0px;
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-mac/enigmail.css
@@ -0,0 +1,300 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * Enigmail styles for Mac OS X
+ */
+
+@import url("chrome://openpgp/skin/enigmail-common.css"); /* common styles for all platforms */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+#button-enigmail-decrypt
+ {
+ list-style-image : url("chrome://openpgp/skin/decrypt-active-18.svg");
+ }
+
+#button-enigmail-decrypt[disabled]
+ {
+ list-style-image : url("chrome://openpgp/skin/decrypt-inactive-18.svg");
+ }
+
+
+/***************************************************
+ * Icons for compose toolbar: encryption
+ ***************************************************/
+
+#button-enigmail-encrypt
+{
+ list-style-image : url("chrome://openpgp/skin/encrypt-inactive-18.svg");
+}
+
+/* hide label unless text-only mode is enabled */
+toolbar:not([mode="text"]) #button-enigmail-encrypt .toolbarbutton-text {
+ display: none;
+}
+
+#button-enigmail-encrypt[disabled] {
+ list-style-image : url("chrome://openpgp/skin/encrypt-disabled-18.svg");
+}
+
+#button-enigmail-encrypt[encrypted="activeNone"]
+{
+ list-style-image : url("chrome://openpgp/skin/encrypt-active-18.svg");
+}
+
+#button-enigmail-encrypt[encrypted="forceYes"]
+{
+ list-style-image : url("chrome://openpgp/skin/encrypt-active-18.svg");
+}
+
+#button-enigmail-encrypt[encrypted="forceNo"]
+{
+ list-style-image : url("chrome://openpgp/skin/encrypt-inactive-18.svg");
+}
+
+
+
+/***************************************************
+ * Icons for compose toolbar: signing
+ ***************************************************/
+
+#button-enigmail-sign
+{
+ list-style-image : url("chrome://openpgp/skin/sign-inactive-18.svg");
+}
+
+/* hide label unless text-only mode is enabled */
+toolbar:not([mode="text"]) #button-enigmail-sign .toolbarbutton-text {
+ display: none;
+}
+
+#button-enigmail-sign[disabled] {
+ list-style-image : url("chrome://openpgp/skin/sign-disabled-18.svg");
+}
+
+#button-enigmail-sign[signed="activeNone"]
+{
+ list-style-image : url("chrome://openpgp/skin/sign-active-18.svg");
+}
+
+#button-enigmail-sign[signed="forceYes"]
+{
+ list-style-image : url("chrome://openpgp/skin/sign-active-18.svg");
+}
+
+#button-enigmail-sign[signed="forceNo"]
+{
+ list-style-image : url("chrome://openpgp/skin/sign-inactive-18.svg");
+}
+
+
+/***************************************************
+ * Icons for compose toolbar: attach own key
+ ***************************************************/
+
+#button-enigmail-attach
+{
+ list-style-image : url("chrome://openpgp/skin/attach-inactive-18.svg");
+}
+
+#button-enigmail-attach[disabled] {
+ list-style-image : url("chrome://openpgp/skin/attach-disabled-18.svg");
+}
+
+#button-enigmail-attach[addPubkey="true"]
+{
+ list-style-image : url("chrome://openpgp/skin/attach-active-18.svg");
+}
+
+
+/***************************************************
+ * Icons for compose toolbar: Protect header
+ ***************************************************/
+
+#enigmail-toolbar-encryptHdr-button
+{
+ list-style-image : url("chrome://openpgp/skin/headerUnprotected-18.svg");
+}
+
+
+#enigmail-toolbar-encryptHdr-button[disabled] {
+ list-style-image : url("chrome://openpgp/skin/headerUnprotected-18.svg");
+}
+
+#enigmail-toolbar-encryptHdr-button[checked="true"]
+{
+ list-style-image : url("chrome://openpgp/skin/headerProtected-18.svg");
+}
+
+
+/***************************************************
+ * Icons for messenger status bar
+ ***************************************************/
+
+#enigmail-status-bar #enigmail-signed-status {
+ list-style-image: none;
+ visibility: collapse;
+}
+
+#enigmail-status-bar[signed="ok"] #enigmail-signed-status {
+ list-style-image: url("chrome://openpgp/skin/enigSignOk.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[signed="notok"] #enigmail-signed-status {
+ list-style-image: url("chrome://openpgp/skin/enigSignNotOk.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[signed="unknown"] #enigmail-signed-status {
+ list-style-image: url("chrome://openpgp/skin/enigSignUnkown.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[signed="inactive"] #enigmail-signed-status {
+ list-style-image: url("chrome://openpgp/skin/enigSignInactive.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar #enigmail-encrypted-status {
+ list-style-image: none;
+ visibility: collapse;
+}
+
+#enigmail-status-bar[encrypted="ok"] #enigmail-encrypted-status {
+ list-style-image: url("chrome://openpgp/skin/enigEncOk.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[encrypted="notok"] #enigmail-encrypted-status {
+ list-style-image: url("chrome://openpgp/skin/enigEncNotOk.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[encrypted="inactive"] #enigmail-encrypted-status {
+ list-style-image: url("chrome://openpgp/skin/enigEncInactive.png");
+ visibility: visible;
+}
+
+
+/***************************************************
+ * Icons for compose status bar: signed states
+ ***************************************************/
+
+#enigmail-signing-status[disabled] {
+ visibility: collapse;
+}
+
+#enigmail-signing-status[signed="forceYes"] {
+ list-style-image: url("chrome://openpgp/skin/enigSignForceYes.png");
+ visibility: visible;
+}
+
+#enigmail-signing-status[signed="forceNo"] {
+ list-style-image: url("chrome://openpgp/skin/enigSignForceNo.png");
+ visibility: visible;
+}
+
+#enigmail-signing-status[signed="inactiveNone"] {
+ list-style-image: url("chrome://openpgp/skin/enigSignInactiveNone.png");
+ visibility: visible;
+}
+
+#enigmail-signing-status[signed="inactivePlus"] {
+ list-style-image: url("chrome://openpgp/skin/enigSignInactivePlus.png");
+ visibility: visible;
+}
+
+#enigmail-signing-status[signed="inactiveMinus"] {
+ list-style-image: url("chrome://openpgp/skin/enigSignInactiveMinus.png");
+ visibility: visible;
+}
+
+#enigmail-signing-status[signed="inactiveConflict"] {
+ list-style-image: url("chrome://openpgp/skin/enigSignInactiveConflict.png");
+ visibility: visible;
+}
+
+#enigmail-signing-status[signed="activeNone"] {
+ list-style-image: url("chrome://openpgp/skin/enigSignActiveNone.png");
+ visibility: visible;
+}
+
+#enigmail-signing-status[signed="activePlus"] {
+ list-style-image: url("chrome://openpgp/skin/enigSignActivePlus.png");
+ visibility: visible;
+}
+
+#enigmail-signing-status[signed="activeMinus"] {
+ list-style-image: url("chrome://openpgp/skin/enigSignActiveMinus.png");
+ visibility: visible;
+}
+
+#enigmail-signing-status[signed="activeConflict"] {
+ list-style-image: url("chrome://openpgp/skin/enigSignActiveConflict.png");
+ visibility: visible;
+}
+
+/***************************************************
+ * Icons for compose status bar: encrypted states
+ ***************************************************/
+
+
+#enigmail-encryption-status[disabled] {
+ visibility: collapse;
+}
+
+#enigmail-encryption-status[encrypted="inactiveNone"] {
+ list-style-image: url("chrome://openpgp/skin/enigEncInactiveNone.png");
+ visibility: visible;
+}
+
+#enigmail-encryption-status[encrypted="forceYes"] {
+ list-style-image: url("chrome://openpgp/skin/enigEncForceYes.png");
+ visibility: visible;
+}
+
+ #enigmail-encryption-status[encrypted="forceNo"] {
+ list-style-image: url("chrome://openpgp/skin/enigEncForceNo.png");
+ visibility: visible;
+}
+
+#enigmail-encryption-status[encrypted="activeNone"] {
+ list-style-image: url("chrome://openpgp/skin/enigEncActiveNone.png");
+ visibility: visible;
+}
+
+#enigmail-encryption-status[encrypted="activePlus"] {
+ list-style-image: url("chrome://openpgp/skin/enigEncActivePlus.png");
+ visibility: visible;
+}
+
+ #enigmail-encryption-status[encrypted="activeMinus"] {
+ list-style-image: url("chrome://openpgp/skin/enigEncActiveMinus.png");
+ visibility: visible;
+}
+
+#enigmail-encryption-status[encrypted="activeConflict"] {
+ list-style-image: url("chrome://openpgp/skin/enigEncActiveConflict.png");
+ visibility: visible;
+}
+
+
+#enigmail-encryption-status[encrypted="inactivePlus"] {
+ list-style-image: url("chrome://openpgp/skin/enigEncInactivePlus.png");
+ visibility: visible;
+}
+
+ #enigmail-encryption-status[encrypted="inactiveMinus"] {
+ list-style-image: url("chrome://openpgp/skin/enigEncInactiveMinus.png");
+ visibility: visible;
+}
+
+#enigmail-encryption-status[encrypted="inactiveConflict"] {
+ list-style-image: url("chrome://openpgp/skin/enigEncInactiveConflict.png");
+ visibility: visible;
+}
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-mac/headerProtected-18.svg
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ viewBox="0 0 56.514998 56.514999"
+ version="1.1"
+ id="svg8"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06"
+ sodipodi:docname="headerProtected-18.svg"
+ inkscape:export-xdpi="2.27"
+ inkscape:export-ydpi="2.27">
+ <defs
+ id="defs2">
+ <linearGradient
+ id="linearGradient8317"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#ff0000;stop-opacity:1;"
+ offset="0"
+ id="stop8315" />
+ </linearGradient>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="18"
+ inkscape:cx="16.413862"
+ inkscape:cy="10.472223"
+ inkscape:document-units="mm"
+ inkscape:current-layer="layer3"
+ showgrid="false"
+ inkscape:snap-to-guides="true"
+ inkscape:snap-grids="true"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1239"
+ inkscape:window-height="755"
+ inkscape:window-x="41"
+ inkscape:window-y="1"
+ inkscape:window-maximized="1"
+ fit-margin-top="0"
+ fit-margin-left="-0.8"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ units="px"
+ scale-x="7.4">
+ <sodipodi:guide
+ position="3883.8708,741.49345"
+ orientation="0,1"
+ id="guide4697"
+ inkscape:locked="false" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata5">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="Layer 2"
+ style="display:inline"
+ transform="translate(-46.896674,-179.42294)">
+ <g
+ id="g6697"
+ transform="matrix(1,0,0,0.95416667,0,10.813822)">
+ <image
+ width="29.903757"
+ height="10.697155"
+ preserveAspectRatio="none"
+ style="image-rendering:optimizeQuality"
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZ0AAAAlCAYAAACZMTQOAAAABHNCSVQICAgIfAhkiAAAANhJREFU eJzt1bFtgwAAAMEYUSQFFBEzMg5LsEVGYBZE78qZIo8U303w3T+WZXmd5/kBAH9pXdefwXAAKFzX 9TXcHQHA+zAdADKmA0DGdADImA4AGdMBIGM6AGRMB4CM6QCQMR0AMqYDQMZ0AMiYDgAZ0wEgYzoA ZEwHgIzpAJAxHQAypgNAxnQAyJgOABnTASBjOgBkTAeAjOkAkDEdADKmA0DGdADImA4AGdMBIGM6 AGRMB4CM6QCQGeZ5vrsBgDcwTdPzsW3b93Ecn3fHAPB/jeP42vf9/AWoNBUXx/LG4QAAAABJRU5E rkJggg== "
+ id="image4638"
+ x="60.220459"
+ y="185.83905" />
+ <path
+ sodipodi:nodetypes="cccccccccccccccccccc"
+ inkscape:connector-curvature="0"
+ id="path4569"
+ d="m 53.961054,182.24537 c 0,-1.18467 -0.02564,-3.06908 3.689545,-3.06908 7.699536,0 24.843888,-3.1e-4 35.936576,0 2.673147,0 2.673147,3.3698 2.673147,3.3698 0,0 0.08347,9.53394 0.08488,21.8929 l 0.0021,20.50537 -4.089856,4.26283 -4.089848,4.26285 -31.646457,-0.0175 c -2.559641,0 -2.559641,-3.32743 -2.559641,-3.32743 V 182.2453 m 31.760891,40.60948 3.825229,-0.015 4.234526,0.015 v -40.03579 l -36.85476,-0.002 v 47.70131 c 10.424891,-0.0325 16.911425,-0.0246 28.795002,-0.0246 v -7.63872 z"
+ style="fill:#000000;stroke-width:0.42511523" />
+ <image
+ width="29.903757"
+ height="2.6499548"
+ preserveAspectRatio="none"
+ style="image-rendering:optimizeQuality"
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZ0AAAAlCAYAAACZMTQOAAAABHNCSVQICAgIfAhkiAAAANhJREFU eJzt1bFtgwAAAMEYUSQFFBEzMg5LsEVGYBZE78qZIo8U303w3T+WZXmd5/kBAH9pXdefwXAAKFzX 9TXcHQHA+zAdADKmA0DGdADImA4AGdMBIGM6AGRMB4CM6QCQMR0AMqYDQMZ0AMiYDgAZ0wEgYzoA ZEwHgIzpAJAxHQAypgNAxnQAyJgOABnTASBjOgBkTAeAjOkAkDEdADKmA0DGdADImA4AGdMBIGM6 AGRMB4CM6QCQGeZ5vrsBgDcwTdPzsW3b93Ecn3fHAPB/jeP42vf9/AWoNBUXx/LG4QAAAABJRU5E rkJggg== "
+ id="image4649"
+ x="60.220459"
+ y="203.46257" />
+ <image
+ width="29.903757"
+ height="2.6499548"
+ preserveAspectRatio="none"
+ style="image-rendering:optimizeQuality"
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZ0AAAAlCAYAAACZMTQOAAAABHNCSVQICAgIfAhkiAAAANhJREFU eJzt1bFtgwAAAMEYUSQFFBEzMg5LsEVGYBZE78qZIo8U303w3T+WZXmd5/kBAH9pXdefwXAAKFzX 9TXcHQHA+zAdADKmA0DGdADImA4AGdMBIGM6AGRMB4CM6QCQMR0AMqYDQMZ0AMiYDgAZ0wEgYzoA ZEwHgIzpAJAxHQAypgNAxnQAyJgOABnTASBjOgBkTAeAjOkAkDEdADKmA0DGdADImA4AGdMBIGM6 AGRMB4CM6QCQGeZ5vrsBgDcwTdPzsW3b93Ecn3fHAPB/jeP42vf9/AWoNBUXx/LG4QAAAABJRU5E rkJggg== "
+ id="image4671"
+ x="60.220459"
+ y="211.76212" />
+ <image
+ width="23.725101"
+ height="2.6499548"
+ preserveAspectRatio="none"
+ style="image-rendering:optimizeQuality"
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZ0AAAAlCAYAAACZMTQOAAAABHNCSVQICAgIfAhkiAAAANhJREFU eJzt1bFtgwAAAMEYUSQFFBEzMg5LsEVGYBZE78qZIo8U303w3T+WZXmd5/kBAH9pXdefwXAAKFzX 9TXcHQHA+zAdADKmA0DGdADImA4AGdMBIGM6AGRMB4CM6QCQMR0AMqYDQMZ0AMiYDgAZ0wEgYzoA ZEwHgIzpAJAxHQAypgNAxnQAyJgOABnTASBjOgBkTAeAjOkAkDEdADKmA0DGdADImA4AGdMBIGM6 AGRMB4CM6QCQGeZ5vrsBgDcwTdPzsW3b93Ecn3fHAPB/jeP42vf9/AWoNBUXx/LG4QAAAABJRU5E rkJggg== "
+ id="image4682"
+ x="60.220459"
+ y="220.06163" />
+ <rect
+ style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.87403107;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect6711"
+ width="2.3547916"
+ height="4.9358077"
+ x="63.249397"
+ y="188.49934" />
+ <rect
+ y="188.49934"
+ x="68.613091"
+ height="4.9358077"
+ width="2.3547916"
+ id="rect6766"
+ style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.87403107;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <rect
+ style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.87403107;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect6768"
+ width="2.3547916"
+ height="4.9358077"
+ x="74.238426"
+ y="188.49934" />
+ <rect
+ y="188.49934"
+ x="79.340477"
+ height="4.9358077"
+ width="2.3547916"
+ id="rect6772"
+ style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.87403107;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <rect
+ style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.87403107;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect6774"
+ width="2.3547916"
+ height="4.9358077"
+ x="84.965813"
+ y="188.49934" />
+ </g>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer3"
+ inkscape:label="Layer 1"
+ transform="translate(0,-4.4914085e-6)"
+ style="display:inline">
+ <rect
+ style="opacity:1;fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.48279953;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect6778"
+ width="50.889664"
+ height="12.689711"
+ x="3.0089004"
+ y="7.8493094" />
+ </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-mac/headerUnprotected-18.svg
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ viewBox="0 0 56.514998 56.514999"
+ version="1.1"
+ id="svg8"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06"
+ sodipodi:docname="headerUnprotected-18.svg"
+ inkscape:export-xdpi="2.27"
+ inkscape:export-ydpi="2.27">
+ <defs
+ id="defs2">
+ <linearGradient
+ id="linearGradient8317"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#ff0000;stop-opacity:1;"
+ offset="0"
+ id="stop8315" />
+ </linearGradient>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="18"
+ inkscape:cx="16.413862"
+ inkscape:cy="10.472223"
+ inkscape:document-units="mm"
+ inkscape:current-layer="layer3"
+ showgrid="false"
+ inkscape:snap-to-guides="true"
+ inkscape:snap-grids="true"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1239"
+ inkscape:window-height="755"
+ inkscape:window-x="41"
+ inkscape:window-y="1"
+ inkscape:window-maximized="1"
+ fit-margin-top="0"
+ fit-margin-left="-0.8"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ units="px"
+ scale-x="7.4">
+ <sodipodi:guide
+ position="3883.8708,741.49345"
+ orientation="0,1"
+ id="guide4697"
+ inkscape:locked="false" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata5">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="Layer 2"
+ style="display:inline"
+ transform="translate(-46.896674,-179.42294)">
+ <g
+ id="g6697"
+ transform="matrix(1,0,0,0.95416667,0,10.813822)">
+ <image
+ width="29.903757"
+ height="10.697155"
+ preserveAspectRatio="none"
+ style="image-rendering:optimizeQuality"
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZ0AAAAlCAYAAACZMTQOAAAABHNCSVQICAgIfAhkiAAAANhJREFU eJzt1bFtgwAAAMEYUSQFFBEzMg5LsEVGYBZE78qZIo8U303w3T+WZXmd5/kBAH9pXdefwXAAKFzX 9TXcHQHA+zAdADKmA0DGdADImA4AGdMBIGM6AGRMB4CM6QCQMR0AMqYDQMZ0AMiYDgAZ0wEgYzoA ZEwHgIzpAJAxHQAypgNAxnQAyJgOABnTASBjOgBkTAeAjOkAkDEdADKmA0DGdADImA4AGdMBIGM6 AGRMB4CM6QCQGeZ5vrsBgDcwTdPzsW3b93Ecn3fHAPB/jeP42vf9/AWoNBUXx/LG4QAAAABJRU5E rkJggg== "
+ id="image4638"
+ x="60.220459"
+ y="185.83905" />
+ <path
+ sodipodi:nodetypes="cccccccccccccccccccc"
+ inkscape:connector-curvature="0"
+ id="path4569"
+ d="m 53.961054,182.24537 c 0,-1.18467 -0.02564,-3.06908 3.689545,-3.06908 7.699536,0 24.843888,-3.1e-4 35.936576,0 2.673147,0 2.673147,3.3698 2.673147,3.3698 0,0 0.08347,9.53394 0.08488,21.8929 l 0.0021,20.50537 -4.089856,4.26283 -4.089848,4.26285 -31.646457,-0.0175 c -2.559641,0 -2.559641,-3.32743 -2.559641,-3.32743 V 182.2453 m 31.760891,40.60948 3.825229,-0.015 4.234526,0.015 v -40.03579 l -36.85476,-0.002 v 47.70131 c 10.424891,-0.0325 16.911425,-0.0246 28.795002,-0.0246 v -7.63872 z"
+ style="fill:#000000;stroke-width:0.42511523" />
+ <image
+ width="29.903757"
+ height="2.6499548"
+ preserveAspectRatio="none"
+ style="image-rendering:optimizeQuality"
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZ0AAAAlCAYAAACZMTQOAAAABHNCSVQICAgIfAhkiAAAANhJREFU eJzt1bFtgwAAAMEYUSQFFBEzMg5LsEVGYBZE78qZIo8U303w3T+WZXmd5/kBAH9pXdefwXAAKFzX 9TXcHQHA+zAdADKmA0DGdADImA4AGdMBIGM6AGRMB4CM6QCQMR0AMqYDQMZ0AMiYDgAZ0wEgYzoA ZEwHgIzpAJAxHQAypgNAxnQAyJgOABnTASBjOgBkTAeAjOkAkDEdADKmA0DGdADImA4AGdMBIGM6 AGRMB4CM6QCQGeZ5vrsBgDcwTdPzsW3b93Ecn3fHAPB/jeP42vf9/AWoNBUXx/LG4QAAAABJRU5E rkJggg== "
+ id="image4649"
+ x="60.220459"
+ y="203.46257" />
+ <image
+ width="29.903757"
+ height="2.6499548"
+ preserveAspectRatio="none"
+ style="image-rendering:optimizeQuality"
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZ0AAAAlCAYAAACZMTQOAAAABHNCSVQICAgIfAhkiAAAANhJREFU eJzt1bFtgwAAAMEYUSQFFBEzMg5LsEVGYBZE78qZIo8U303w3T+WZXmd5/kBAH9pXdefwXAAKFzX 9TXcHQHA+zAdADKmA0DGdADImA4AGdMBIGM6AGRMB4CM6QCQMR0AMqYDQMZ0AMiYDgAZ0wEgYzoA ZEwHgIzpAJAxHQAypgNAxnQAyJgOABnTASBjOgBkTAeAjOkAkDEdADKmA0DGdADImA4AGdMBIGM6 AGRMB4CM6QCQGeZ5vrsBgDcwTdPzsW3b93Ecn3fHAPB/jeP42vf9/AWoNBUXx/LG4QAAAABJRU5E rkJggg== "
+ id="image4671"
+ x="60.220459"
+ y="211.76212" />
+ <image
+ width="23.725101"
+ height="2.6499548"
+ preserveAspectRatio="none"
+ style="image-rendering:optimizeQuality"
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZ0AAAAlCAYAAACZMTQOAAAABHNCSVQICAgIfAhkiAAAANhJREFU eJzt1bFtgwAAAMEYUSQFFBEzMg5LsEVGYBZE78qZIo8U303w3T+WZXmd5/kBAH9pXdefwXAAKFzX 9TXcHQHA+zAdADKmA0DGdADImA4AGdMBIGM6AGRMB4CM6QCQMR0AMqYDQMZ0AMiYDgAZ0wEgYzoA ZEwHgIzpAJAxHQAypgNAxnQAyJgOABnTASBjOgBkTAeAjOkAkDEdADKmA0DGdADImA4AGdMBIGM6 AGRMB4CM6QCQGeZ5vrsBgDcwTdPzsW3b93Ecn3fHAPB/jeP42vf9/AWoNBUXx/LG4QAAAABJRU5E rkJggg== "
+ id="image4682"
+ x="60.220459"
+ y="220.06163" />
+ <rect
+ style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.87403107;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect6711"
+ width="2.3547916"
+ height="4.9358077"
+ x="63.249397"
+ y="188.49934" />
+ <rect
+ y="188.49934"
+ x="68.613091"
+ height="4.9358077"
+ width="2.3547916"
+ id="rect6766"
+ style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.87403107;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <rect
+ style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.87403107;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect6768"
+ width="2.3547916"
+ height="4.9358077"
+ x="74.238426"
+ y="188.49934" />
+ <rect
+ y="188.49934"
+ x="79.340477"
+ height="4.9358077"
+ width="2.3547916"
+ id="rect6772"
+ style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.87403107;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <rect
+ style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.87403107;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect6774"
+ width="2.3547916"
+ height="4.9358077"
+ x="84.965813"
+ y="188.49934" />
+ </g>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer3"
+ inkscape:label="Layer 1"
+ transform="translate(0,-4.4914085e-6)"
+ style="display:none">
+ <rect
+ style="opacity:1;fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.48279953;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect6778"
+ width="50.889664"
+ height="12.689711"
+ x="3.0089004"
+ y="7.8493094" />
+ </g>
+</svg>
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..bdf46e6f46c92af3d5728711498456b1d7e8a2d4
GIT binary patch
literal 6834
zc$@*U8cpSiP)<h;3K|Lk000e1NJLTq003M7003eL1^@s6-Ji6D00006VoOIv00000
z008+zyMF)x010qNS#tmY79{`x79{~mQY7#I000McNliru;Rp&5EC=+k{%8OI8b3)y
zK~#9!?VV|m9oKc=fA@C3e)}w#eFht`k^m`gBB+RzsD&aeQX*r@vf_$k<wK-cskl`6
z7Q2#4<wGh_RH=%U$YrZsjwL&e%9SdK6iJa3DN><C2?7C<Al4ZyBruqLdGq$y-S?h+
zcyIRi27|!>7|6U;T`0`->v{d}bN}a@|2^lnI)DB=T5AB5Qp}E(ezw~GH~QJ-##%me
zvy9)|JNp3e55@<8-+<w+<wKRQLOHDbv-8>azd2c%?mC@0$6#)lye(1)O9-;yiRzo$
z3>>9N=s4S&`e=!^9Nyo$>-Tz6-2<J8cGA%l3bpbDL?8$t5&@-F54aVJh)@hlf0XkJ
zJEr{%{WE9mcxi%TQ>QsQ^FDb~Bp(#93jn{_=bft@Qcj#Hf1I}1CUG&}DVy?$5&OSF
zF)u|OkGLB{E4?!4wV_rUr4;<)zs?2uC!fFY^8H80kEf4j`WY#np&}Km4zYw-ZLnB$
z?J}a)3b@>Oqy?mQ0}74CMT^E2mraqL_d4TS4nMNx!QmZE+kX9-EuZ+CbToyl-K(dR
zit$@UOXGj>%i%Zg8Y&L=9G*Csyyy=zVaF&q87vkeAWj4#4p9|FX$N%`p`C05zyUD^
zBL-uAj0rGe2z<?i9_}rMg|`1U7UWN5JGYLM#_xNe`@qGnM90&;>7IVQ0?n)lcxXfN
zLH--3Ci@Tk==~pW@6VrSq;eJ;2~mzgCpC^%=tu<Zr63X^={6H_Tq4T73cxo6+*C|J
zU<1lw9_!B_#t?b|zR7tNoArJ<HJps8)F<DaJddNC)B9R?f2uvc>9d~WHKdq^lwy@|
zWcZywKb9T%{I^bjx3?(o6RHYfBtRUC<3{Li-9>Y36Z@KW(;n*}=Eg}mNfa72`v=$J
zGdDouBM68kl#rq+F<u%cA7(k4ImJX_keT8z$_K2%F&-(kmF{RC|Ni!WmD$&}_t^HP
ztsh&pg;$K<lMf3|9nbdfJ((Too$`kuQiO<wh(|i=5sNj`+q9Kdua)jZH%*ad;*mHp
z6+>wSv(xG(1-$C(ELIG08^_U#e3)d~6p1<t&w+`;G=zrG_sIlfNX7E@^of>uB(b?Q
z+Coz_y)25U3;0NJ?Ee1D*{}WS$?tBR3Wvd)Ld67eBalwBz2$c9Y~9b^)DEJKht`_V
z8kANTf||3Gn;vi_3WXx-c=Sbk0oc>Dli~6h7fVAtGyD>T@)YE!Q7X%f$??q-f5DfE
zBikd&J#=69UH3(uXk)+yA@3LZ4vrnU<Jk)@CbQ~7nrNX4b}B_8*1{*dKSo!)i;n0f
zD$2)~0I?PkP@+-D!YF7>0ixbOap9~3RCe|uXyRUsTih)?p&w?rG{S46zeMuWs31g`
z<nYuRRQ%$H`%>M<JL4VulU||$;3CXavb%?hLyw%vpKA$K0U{Rb3Q;djJhqAML@%vg
z6A|qZN&q5QRfAQWIuYKLLvA->0xZ=ySBt_`4lyT6Pof(GBx22!DtW|)DA#AqjMAS!
z!$f(Cgq!#eNiXpq>jGXd#q<joU)k~Nsb6g#HYZzb!s0~1rd&SKaTh(QJ`xd+G9?PO
zh@(~2f~B!J>$L>qR<U7+QXrrNBHE!lzKPHGe3Do7j~OeDBH4gkWt{V2f#)y2v~PdQ
zZNK|FyPjAMxNm%NmHY$8GbjJ?NM*zm*MbWY_2@|Sk&HIsIWZ~}F}7;e+PF2}XjcGS
zug#h^>JU}U90h#kAj*=A#A%QB0GjdgAYu(cQ0Cp4<7n+}DTifdL&Z73OF@a5O1A4*
z?$jM4X1v#WfCn)zN^f!-$w(aSSaQ~{g-ldqO<8|F-pF{*JJjkI)o47&r9Iw-(hifk
zA&fN`YdDb`AnvA;dB5<T=9v4{MSu@v&VS{6@%*C~O9K=mdGLUk#*4;jk8PqTW&F@b
zX1&Mk^jb{5>k@HXx5P)um09+JplkkC6<QpUkrXJ4iaS7nSYbSSp7#qeeR$%X-#gHC
z&sSTcE%Sh9f|<{ZS0)|_wI9VX-~bU9<$5S31QK9GEm8tkCyQ)MN)f=;9C{-qLFOP;
z=sDwBihx3iLX@p0j*%$%7My@kRVY$mxHPIN)}Q0>G?44hp6xi6J(adz2$9-vybi)h
z6CpljF<84KjMq0O;1*Mb&96GZs$-Wr=T*Z(tsI<)2CWDQ=99VwUpFF}K!u3c2Eil*
z0V<}*syv4#-by~ub3fhjPF$cB$W8f^+sDe|)oG>yt#Gu$(Uzi=s$Wr721c(FKZsmQ
zs4Y<&C2dmN5$+{M6h}FAEVoJ{N@*O&;e<NPG#N5d0tC(C5ZD(*AO=?}tk#GYL_;7x
zL!}{nGjHK8z(ggpeY8A@iAa^3wZc^z5DK=0!7f8$DKO4P2D}zc74hjd?X;K_9}N!B
zqML|BT%-=*3I!U((G1B5Q<7oE=5SEBOYYV|6N)9G1*3(qX0&BfX0SLy#a0%Fg+(cm
zrC3d=bk)jzd@PDdo2@M>*d<>`mRPv6VIvL;gks54;zQidqmfVX(2fU5c}bMpIPRYc
zr#V<S%#n#xobt~T7ll^KV=slmh)^Ivl&P_V5Q-ra<_R&20T&DwQ?u?_iwLlys*4Pm
z<rcln6rFY9W4Q(<ZfiFj594%r?QDtlk%}dWN8$~pCRC(CNy_wxXPGh6luQ;!$7|u>
z^6=M+Al9!w-UZLlC^H9xyJ+TXJHJY!1en2y!?X!%0GlPiC@#n1a>D`F)k94Y6^D<6
zpXMV?ck_?h4iNKV4IRHAjQf+kTRh3P$N!44@B-FW$V%8C2G$@JWCpO+gj6fUE(qEe
zr}i=rv=&exHUwk9nXa9(<c~;X73f(u1c`@+q-y5L_+#9eyq#n^S(B0L4_|zKka>oq
z#djI4oFTvm4B)S*E{e5)MU<*yxd7}s@@#_}wWjWGg<y3;{stIPiPNlF*cIDGN3;#c
zbs9e#2`Shj{iOlUmCxauVwEBkuw<5Bshupba9ho&Z0*ycz{=e0QFW*5YTVTtR$JD2
zJYXMUpSzEbc7BBBL^CT@Hw;!TalCYbA5Q&WM*WcmYT+t{Hw#LnO1YvIVc^yWNYyud
zS%X7di(0tecw6DF#2wrdyO(4<xuSLZHlV*ez$@8T$%Zq8HdroUtWDc(1o)CUax*AN
zmyt`N#^a8}e(p`)h39z<x!WQXZIM%@)4V$KDkW2@I`At1T*=B#-(76kH#xdXhY`9}
z4^MheaevDJcBXf%D5e!{k!Po$=e5jhoX8y`&;jW6(B|zbNLbWOkH1Td7QKnvV*6<F
znu)lP6{C`@$?<OKT?T_ed?{ZYiAa4fI%nakM)u38M=gjL<;wY4*7|L)ezclac6d8^
zs^dx8lkHW>xnV{*#reuP{%qpg<oztDD^qV*{O${IT0kjgjelX|4G|$KF@7iU6nCcX
zq+?SDp1X3+@KWX#-YL98Sgur;(A0V^^EHf8?DdfBuKCd75GTeSZ#O%l+lhK!L-Mp(
zeDOI~KFdI<A7jG#n0pNmS}mK^rcsnq&5+bw5u+>v-!5g8rCDU186r1;Qo8A47d!b(
z`@?KbY;LHP0OU-LxARANZt_PQExbdZ%=*X+HH_8e<M0h8^Id5eXcV_Zw((HxA=*-H
z#G~<s=LQlm>W}jL^z)4Rqxd$wx>D(e)eE*!rg`&oxE6<9k=;C$c!=hBGhW1Nn9UYV
ziA(+vFJzu&DwwR!?rY;!8i`kS`8jrh7J0c$vrg2a)=+x9HX2eSi1BIXVIFRJnESi#
zC+5Z)a&aI5FHFC{k^B)(X8Q@WU%k((L0r^46)mFWMsc{*w3M|dd>bI6bcwJhx`&=<
z4+$^PFw&@sQw5Hdk26p?Ln!{W%)S;Sl`g*_L#&W;QasiA6uX*s;lvwz%ACz{!N15~
zPy8i=<>A_cxjuC2F1Bu|6=l|@hAgIbwE~66YGe3okSr-He8T+{`=Yn8yKOfuiIx@5
zKbk+vOPQC*l?teEcJ`}vnPnn1#c&QkwTL6v#ljtNFk<TR<uZ0s>rW+!W{0zbz207$
zqD>7`ctNO0h2dbB(}mLn##F&j*SeOmE~OZ*RJ#@}#H>eK*h(eH;}a|cbauJbVli6C
zdwx}|Q|mQF9a>29Xxk$kNZn5)9%*=OUwn>a5A(|O%RD{x48{i6iSSm6!)Jx_dNfcc
zd}wuB`0eCVM2&|Qy)0pHwMLY}T1!z1yzalj5F<>;#L8B?%5bJdwem^tA-1Qsvnkcu
zu(u|ZkZhRcm6?}0T{w*~^E&ssDH?UOCS_$2Engj_2(-FwJlgVEw#hb9I#s2Xg%++g
zN+~1|ro(B<lRk&+Ato6^l)kE)b41gwJ9#AjTWm>gA)QP&JiTN~Ooo%ZoOy|X^0|3{
z)b%ZmcCUszwGk$QG5)&pSNz-FKVe65$FgeKIsI9nBkbTSv42T)(&N2cKRKteHqdKe
z?^U;QZ|oi(>wSz(iA^h}@ULcG<yheuZ_gZ~V2ibzxc+tMSM4;*5;7W&l9L=|RVM8$
zZX{YH_-knQ+S%l_(W2TY$|wf2CeTG`+EqL4PCIEYT`O0s?<kEJMuQQC{7aN=1xt8C
zz;3|dOES#`Li$3kvr>gMAIxxY@=czJ{)mf}i+rK;3wWx2I_)|xTf4UM7>|?o)BKm>
ze`QLh(Mz25<uXkX@bLIj^FLu<(>_wosfHRz#a0-pjPSjQzva!`LHt^hqpm^REwa$x
zRS&zG`pZ>m7B%!O)dt$17hdPZ%!^zqU!q_NjX&pV`jVUZ@TLRo*ZbMV)+_c&9dz;`
z=XUnD>}Pv=`-;Up)8RC)&Ai5Vag1`szfp#L1(dSD!`da$%Um?0;7ImO1}m5NgU&x7
zq9Y`oWCQYtu6P%T)&zHr--%~EF3N?)Hok(1i-lg@!`;r^>~7jkN4%rqxuJwi1{1t8
z{W8;)44C=8uWKgvi>#Pz=mkObf<mDMp^Tb8w321VY?f(4nDMiGYw*AG^O@J@szvKQ
z<)}+ja}!_g{O5eJ^$RowF(Ts3T~jIQ9<x*G?)csO(~jS#HQkCEa~miahIevr^Lpmz
z{BYuFrp)-9Q*Ctpt34L*?Gh?hS3oJUkdM@|W2pjnp)|;a(glVq!-R8nc+0I^YfWde
zlg)`O?1|h;j2L38*OXd{y!3F<Y}Q-Y9NSD!qNlohgQ`y@fmG{D&X&)T5A%d#=Vof7
z>tF4$8d+l9x@nZ72$ave`D45`^)p_We1YO@_*L(GrRZz#<DQOt`C9txY}Q*=sv-je
z3ERXMqhH_?&7WXr$4*?QzI6vSU?#}&cVmCcvy;!v*EVf{ZbWxkMp-vXssHrUvW!#^
zCA?X9i!n3C9Zh$zGroh~XkP;n23is=d}_-ldGq|Q*<@NdWDnsJE)I!A(L$QdPA{MD
ze1bjcJ=M0D24kQ!z@gkBj%80W=8s&x&V8k84a|#HOEFsaelevq88gY5@;NS*E-~tl
ztynAQX^+lCC)>U4+~VAVZm{h~t7@go>0nD@3(e8SwSl38j07W`DxPAxlA#oq*+^sk
z^>9@!0u914e>wae?rXW9+mg58X|Hb2rg9Ws%HxU7C)pR@&(8*b!nh78>u~N`7~!+&
z$GInQH=Elw6N|(ef<06j;>GC~__N{vK|%ZtK_d+;ybhgQ#V{Do<<E0`<~WD5hslTe
z70>TTb+Du57CsgG3_IPOa~VUF2wT)<?n>RshnnxKO0Ny(W!aYbS>|W-=LabGl^d46
zFT-y^pObr8RZ%TmfKV%EgbRLefeYn992Magnb+xy_K|dxI1L1k=2SDT=kiF?XDJlQ
zoc7NV>VPN-b~-!Q-?X1wn{LI?^*?XKkTW@6o%$(f@@F7W^LHoehJdZ={9U*1f`K$j
ze3tr1yRtl~6&J%nzIo}t(Hq~)0Xe{~#IE}7;10y2aUR|LTNKBN%=j68?!Q5w*TuhR
z`zkwIc9Ke_8dAiG!g2bG{X94IJkx##oEud6D-BA$SLHP2>iImd6{h`Z&KA!yP#PeR
zprI4hN|TDF=!^BSH@cT?Y8zXf?c9>Qg?J><u*}$Cc(42({iS|#L6*S!8-lN8rDD!&
z`ow+3)1yD&Wd1k@(g$dbwBpu_MifMn5$+D}q1|@UX}ai4baHR^y?9Ztu5}v>MN{PI
zi63xq=GO#aFn6tuIIhd8y*R?T!h7Vy63<OM%YDrU*q_|r@cd1wO(eVoZB3i-Tn{ha
zxQKo#nBr&Cukm)~UCtFQ5Gun)9@if5S^|?d1$1Sc{=xuTVq4jh*h56s&t5#&!*e_m
zPPMkA5!j)G<boV03MZKKCn$!68(a7xv%Niyr&xVGd@kdZMO>~f;}BL-;6w>I$@8tD
zKcz&89<PVp$=&rquchIQgsEy2WEt^CI8r#wHwV8#$*(MmsBRSC^B79q@OrV+v}*AO
z)_GX5l>7=Ob0_)9)KAEp{IWIBRmb^aIGFtvN3usKR7x1J8>_zEE^wSiR=J3L87<{X
zyV{f}#0UfV0X*&USjS^{%4@8huK;$@7I|a(7o5l)M*_g9wJh9F3s;vdb9NVMxN1(q
zlm_CJsz=XP6h<no7%E+2#?SJ@<{z>zwU5U;9>1dd8Y~ZTvT%Z@N1ox5e~GK#U3i_7
z9;+77%0is3|9*7}LJ26C0w)S5=r8qCFoo)-!K<37mhoVm{z5;~VTQ8tH*5%a87<{{
z&OA*(!1qSK$M;9R&zaI0N~W|50}{%n%%SW-{$}KR6vF)SMz5O<a3zgJtl1^FMLqj#
zIcBdHY6gpgeEa?X$@$WG{L5sBB~#+Xi5L0B<S#inGeAXx>LvnL^Ig{QP_ymwjVO0T
z3%?GAqr_4SioBb9moa~goXIsz`)3{HslrJHOM?`G4Q|~HzS|A04;Q7$n=Efly~T5*
z&vB`Ai7)kjsji|p9*lFYe2#Aq{VC(+>5Z|A{<>$H%fdmaBP}kp*tLNWisg-&H<&OJ
zJlOgm-SO^aq2k-ww>Vxn&U87CZ^Aj5d&61z5?Z-Dr7S8WzdpHyDB)1{O@2D{Dwisk
z$k!<(dOP<HzncCvVI>43h*DMl-f&d17(->HKHxE(An7#SkQEZG1qp?0d4}%}ewTxp
zU(Pwuyvg%c_6R?n_$jYVzDB5St}xKi9!*a3oI0w35FoDOB%Q?Sgpq6FKovn~IF&!e
z#nK?TFo(rbwq@QcooA>r#DqVNoh`f(+2Brdma{cUcfQ!vh3Y;^TNB&J`Z>;J->+`U
zVp-g)QZJLg+>+ZBnYmD*%)!Y+w0j+-bdt|^Kf!y&^L*>QZ*i$`v1%Ib2JcFr?Y^`M
zu|mR4u)k@)Bx*j%rP^ykU%F=~FWFuv1EHDcqU$YBSOQKJPw>p7!gV5yRE9ZII7iu*
zYAO5%k4|JkT5lntlI(5WQ%JapIl%J(y~)kPWvTQ!L_mVYjh!pkC$2NMX!^@%c;CK<
zr=#S8JcFf6bDLLgXv<#Gnxjx8-2{7D_6{f9_#EIxfZa{Izn^kb@xEBkzF|8`-sZt5
zloIB*30Id<3uPS)q$+1tN&(LfJy+Yj@+Q~{s<t7UGSz5VSw&~0oo&gj+}rWtucy59
zk|@QE{UGHfk9J18s_){l+&HqV?S_^&1&XH3%`|5FG_`2DVqJ8`x=BWoKVIlG0Z2p=
zXWLZ!%*VSvK4!<IWh^)9NvIHAeQI;b-}RuDn{A_<T6Gw<`mZwJ*a#1{e~jB(Za<Os
z()BaVxEufDPxn5Y&6vy=&lS&ZpO8t!7-m~yM9HG{MYW2}4c1YH*tjMwQtfEA)gU`X
zlI}=1pXqyKdRuba-!;rM9py}SC%RwnPIS`~>#DX3SWu>RScMW>U8wkA%v*+4$gFi+
zMaoIj6Yr)k(f3A2tm8j51YDs=d+8rN)cJ}3^OaqH_(F?LL#V55_y&}<h|$%Cs(<L9
z*TO!GLYZ3kl&vlyKG61n|3`biKG2uy`&`mXuB@df6m7A#Ke?^tw)a2R_4tVgTR(b2
z8RaVr5>`Q1vrqINBIr^B*C?=6u*FoVB(CB-(fv6-*732SJ<WUmKB}Xudly@Cq?xpr
z{=>(*9(&+u{^*kjvxmNBt?{g?K0zgdI0`Wulq{g`TJWgWVZW#il%Cg(x1|cQpiov3
z#iPS%KlR()PZjQH{m@UgrndgOdY5b{&$`Ox!yWhiVn^%FyLw~27rsCEzgx~0&h?J?
zlZaN}SY}H~loC+YZp&&(w^|==+(@7==v9^3kU3Puh|;Co^S^(~m$@~4>-fjJKmPH=
zLQQJ5fCEIGC@r)+yCbzz-=4nn%W0>%_eAL=8JnSuPdTX8U@J$@eJNGjC_=8n$~Ow!
zN|rqTTA7Dd4xVyp^V(?9&Fqcue)G1}tpm3tb{tDO$%b9ga0Tx~&IkEF&W5>tPhb4O
zhmT|q-}-j$EyjZ>LPC@aQhT$<y2R2OX*Fk-FK1f;Luhi-bVl2Fyz_~}yOX>5Y~N%5
zrpar1eKqeyo(DV#YA^nY)CJsFgH@Kv^3>l?hU5FkD&w5Wo!mEF$#AN0iuVieQM3he
zHeZV&ufoDN2;j;p92Fs{(`-v@r`2s^f75NUHL-bWb8^efmgLrFlTPwKw|Q+Nj&?5p
z%JoH*av9!qv_nKk=uP(g?xwIcvN^itOK}~0bi6Wtn?m)t*6GdJ99hXATxA8T-Z<7B
zQ4!L*nO%w9ba)-y-EwzfYkccB`jUO$Oh?mK{I0oGQ_8}h&91DAs=oauv>_IIKq+-e
zu#3v5wAL?QG2U9A+endM^)>6d`nEvrqEz)KY7IKangCzz{~P^m6@dT7hVj3-clH6`
gAB+zGzX9X_11S?Li!{K2tpET307*qoM6N<$f&qAHng9R*
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-mac/ok-sign.svg
@@ -0,0 +1,147 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="14.996866"
+ height="17.165913"
+ version="1.1"
+ id="svg38326"
+ sodipodi:docname="ok-sign.svg"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06"
+ viewBox="0 0 34.992688 42.914782">
+ <metadata
+ id="metadata38332">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title>Highlight</dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs38330">
+ <linearGradient
+ osb:paint="solid"
+ id="linearGradient4972">
+ <stop
+ id="stop4974"
+ offset="0"
+ style="stop-color:#d5d211;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ osb:paint="solid"
+ id="linearGradient4964">
+ <stop
+ id="stop4966"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2780">
+ <stop
+ offset="0"
+ style="stop-color:#ffffff"
+ id="stop2782" />
+ <stop
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0"
+ id="stop2784" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2834">
+ <stop
+ offset="0"
+ style="stop-color:#e59a00"
+ id="stop2836" />
+ <stop
+ offset="1"
+ style="stop-color:#faff7d;stop-opacity:.18367"
+ id="stop2838" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3057">
+ <stop
+ offset="0"
+ style="stop-color:#282828"
+ id="stop3059" />
+ <stop
+ offset="1"
+ style="stop-color:#282828;stop-opacity:0"
+ id="stop3061" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2713">
+ <stop
+ offset="0"
+ style="stop-color:#000000"
+ id="stop2715" />
+ <stop
+ offset="1"
+ style="stop-color:#ffffff"
+ id="stop2717" />
+ </linearGradient>
+ </defs>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1199"
+ inkscape:window-height="731"
+ id="namedview38328"
+ showgrid="false"
+ fit-margin-top="0.2"
+ fit-margin-left="0.2"
+ fit-margin-right="0.2"
+ fit-margin-bottom="0.2"
+ inkscape:zoom="12.183333"
+ inkscape:cx="11.164775"
+ inkscape:cy="-1.8436479"
+ inkscape:window-x="46"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="layer6"
+ viewbox-width="18"
+ viewbox-height="22" />
+ <title
+ id="title38274">Highlight</title>
+ <g
+ inkscape:groupmode="layer"
+ id="layer5"
+ inkscape:label="Layer 1"
+ style="display:inline"
+ transform="translate(-9.0161178,10.157503)"
+ sodipodi:insensitive="true" />
+ <g
+ inkscape:groupmode="layer"
+ id="layer6"
+ inkscape:label="Layer 2"
+ style="display:inline"
+ transform="translate(-9.0161178,10.157503)">
+ <g
+ id="layer3"
+ inkscape:label="Layer 4"
+ transform="matrix(1.9578174,0,0,1.8768146,1.7731116,3.9800016)"
+ style="stroke-width:2.96904826;stroke-miterlimit:4;stroke-dasharray:none">
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#008000;stroke-width:2.96904826;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 5.1029077,6.1459338 c 0,0 4.0105782,2.9252466 5.7695983,5.6644272 4.278402,-11.00488643 9.869697,-17.5760024 9.869697,-17.5760024"
+ id="path4586"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccc" />
+ </g>
+ </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-mac/password-error.svg
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="25.169001mm"
+ height="25.169033mm"
+ viewBox="0 0 25.169001 25.169033"
+ version="1.1"
+ id="svg8"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06"
+ sodipodi:docname="password-error.svg">
+ <defs
+ id="defs2" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="2.1594276"
+ inkscape:cx="139.34847"
+ inkscape:cy="68.416928"
+ inkscape:document-units="mm"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:window-width="1399"
+ inkscape:window-height="855"
+ inkscape:window-x="41"
+ inkscape:window-y="1"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata5">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-8.7120523,-4.2052231)">
+ <path
+ style="opacity:1;fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.83094561;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:14.39999962;stroke-dasharray:none;stroke-opacity:1"
+ id="path815"
+ sodipodi:type="arc"
+ sodipodi:cx="21.296553"
+ sodipodi:cy="16.78974"
+ sodipodi:rx="12.5845"
+ sodipodi:ry="12.584517"
+ sodipodi:start="0"
+ sodipodi:end="6.2656622"
+ sodipodi:open="true"
+ d="M 33.881053,16.78974 A 12.5845,12.584517 0 0 1 21.351682,29.374135 12.5845,12.584517 0 0 1 8.7125354,16.899998 12.5845,12.584517 0 0 1 21.131168,4.2063099 12.5845,12.584517 0 0 1 33.879121,16.569231" />
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.08675575;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 24.849867,9.3258801 3.897186,3.9041669 -3.775033,3.781809 3.51511,3.52141 -3.61439,3.620874 -3.515111,-3.52142 -3.614391,3.620879 -3.897185,-3.904175 3.614392,-3.62087 -3.591526,-3.597964 3.614398,-3.6208701 3.591517,3.5979611 z"
+ id="path817"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccccccccccccc" />
+ </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-mac/sign-active-18.svg
@@ -0,0 +1,340 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ version="1.1"
+ id="svg38326"
+ sodipodi:docname="sign-active.svg"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06">
+ <metadata
+ id="metadata38332">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title>Highlight</dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs38330">
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top"
+ id="linearGradient38877"
+ x1="0"
+ y1="0"
+ x2="7.7620873"
+ y2="0"
+ gradientTransform="scale(0.64415662,1.5524175)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top"
+ id="linearGradient38879"
+ x1="-1.9364917"
+ y1="-11.61895"
+ x2="1.9364917"
+ y2="-11.61895"
+ gradientTransform="scale(1.2909944,0.77459667)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top_center"
+ id="linearGradient38881"
+ x1="-1.4142137"
+ y1="-12.020815"
+ x2="1.4142137"
+ y2="-12.020815"
+ gradientTransform="scale(1.4142136,0.70710678)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top"
+ id="linearGradient38883"
+ x1="-2.236068"
+ y1="5.0311527"
+ x2="2.236068"
+ y2="5.0311527"
+ gradientTransform="scale(1.118034,0.89442719)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top_center"
+ id="linearGradient38885"
+ x1="-1.8708287"
+ y1="4.2761798"
+ x2="1.8708287"
+ y2="4.2761798"
+ gradientTransform="scale(1.069045,0.93541435)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top"
+ id="linearGradient38887"
+ x1="-1.0473186"
+ y1="10.741717"
+ x2="1.0473186"
+ y2="10.741717"
+ gradientTransform="scale(2.3870482,0.41892744)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ osb:paint="solid"
+ id="linearGradient4972">
+ <stop
+ id="stop4974"
+ offset="0"
+ style="stop-color:#d5d211;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ osb:paint="solid"
+ id="linearGradient4964">
+ <stop
+ id="stop4966"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2780">
+ <stop
+ offset="0"
+ style="stop-color:#ffffff"
+ id="stop2782" />
+ <stop
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0"
+ id="stop2784" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2834">
+ <stop
+ offset="0"
+ style="stop-color:#e59a00"
+ id="stop2836" />
+ <stop
+ offset="1"
+ style="stop-color:#faff7d;stop-opacity:.18367"
+ id="stop2838" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3057">
+ <stop
+ offset="0"
+ style="stop-color:#282828"
+ id="stop3059" />
+ <stop
+ offset="1"
+ style="stop-color:#282828;stop-opacity:0"
+ id="stop3061" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2713">
+ <stop
+ offset="0"
+ style="stop-color:#000000"
+ id="stop2715" />
+ <stop
+ offset="1"
+ style="stop-color:#ffffff"
+ id="stop2717" />
+ </linearGradient>
+ </defs>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1199"
+ inkscape:window-height="731"
+ id="namedview38328"
+ showgrid="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:zoom="14.75"
+ inkscape:cx="6.2137977"
+ inkscape:cy="8.1121029"
+ inkscape:window-x="46"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="layer6" />
+ <title
+ id="title38274">Highlight</title>
+ <g
+ inkscape:groupmode="layer"
+ id="layer5"
+ inkscape:label="Layer 1"
+ style="display:inline"
+ sodipodi:insensitive="true">
+ <g
+ id="add"
+ transform="matrix(1.3457681,0,0,1.3457681,8.0886149,9.9113851)">
+ <defs
+ id="defs38291">
+ <linearGradient
+ id="top"
+ x1="0"
+ y1="0"
+ x2="1"
+ y2="0">
+ <stop
+ offset="0%"
+ style="stop-color:#e4ba2f;"
+ id="stop38276" />
+ <stop
+ offset="100%"
+ style="stop-color:#c36a00;"
+ id="stop38278" />
+ </linearGradient>
+ <linearGradient
+ id="top_center"
+ x1="0"
+ y1="0"
+ x2="1"
+ y2="0">
+ <stop
+ offset="0%"
+ style="stop-color:#f7e1c9;"
+ id="stop38281" />
+ <stop
+ offset="100%"
+ style="stop-color:#f4cfa5;"
+ id="stop38283" />
+ </linearGradient>
+ <linearGradient
+ id="hold"
+ x1="-3.3561783"
+ y1="-3.8734536"
+ x2="3.3561783"
+ y2="-3.8734536"
+ gradientTransform="scale(0.59591592,1.6780891)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0%"
+ style="stop-color:#f8edc3;"
+ id="stop38286" />
+ <stop
+ offset="100%"
+ style="stop-color:#f4d644;"
+ id="stop38288" />
+ </linearGradient>
+ </defs>
+ <g
+ transform="rotate(45)"
+ id="g38295">
+ <rect
+ width="5"
+ height="12.05"
+ transform="translate(-2.5,-7.5)"
+ style="fill:url(#linearGradient38877)"
+ id="rect38293"
+ x="0"
+ y="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38303">
+ <ellipse
+ cx="0"
+ cy="-7.5"
+ rx="2.5"
+ ry="1.5"
+ style="fill:url(#linearGradient38879)"
+ id="ellipse38297" />
+ <ellipse
+ cx="0"
+ cy="-7.5"
+ rx="2"
+ ry="1"
+ style="fill:url(#linearGradient38881)"
+ id="ellipse38299" />
+ <ellipse
+ cx="0"
+ cy="-7.5"
+ rx="0.75"
+ ry="0.34999999"
+ style="fill:#3d2c0c"
+ id="ellipse38301" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38307">
+ <path
+ d="m -2.5,4.5 2.5,4 2.5,-4 z"
+ style="fill:url(#linearGradient38883)"
+ id="path38305"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38311">
+ <path
+ d="M -2,4 0,7.5 2,4 Z"
+ style="fill:url(#linearGradient38885)"
+ id="path38309"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38315">
+ <path
+ d="m -2.5,4.5 a 4,4 0 0 0 5,0 z"
+ style="fill:url(#linearGradient38887)"
+ id="path38313"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38319">
+ <path
+ d="M -2,4 A 3,3 0 0 0 2,4 V -6.5 a 3.8,3.8 0 0 1 -4,0 z"
+ style="fill:url(#hold)"
+ id="path38317"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38323">
+ <path
+ d="M -1,7 0,8.5 1,7 a 2,2 0 0 1 -2,0 z"
+ style="fill:#3d2c0c"
+ id="path38321"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer6"
+ inkscape:label="Layer 2"
+ style="display:inline"
+ sodipodi:insensitive="true">
+ <g
+ id="layer3"
+ inkscape:label="Layer 4"
+ transform="matrix(1.0666667,0,0,1.0666667,-0.27745623,0.00654138)">
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#008000;stroke-width:1.74388254;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 11.90849,12.21419 c 0,0 1.39836,1.035106 2.568841,2.4924 0.454732,-3.871985 1.725652,-6.1303502 1.725652,-6.1303502"
+ id="path4586"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccc" />
+ </g>
+ </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-mac/sign-disabled-18.svg
@@ -0,0 +1,368 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ version="1.1"
+ id="svg38326"
+ sodipodi:docname="sign-disabled.svg"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06">
+ <metadata
+ id="metadata38332">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title>Highlight</dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs38330">
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top"
+ id="linearGradient38877"
+ x1="0"
+ y1="0"
+ x2="7.7620873"
+ y2="0"
+ gradientTransform="scale(0.64415662,1.5524175)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top"
+ id="linearGradient38879"
+ x1="-1.9364917"
+ y1="-11.61895"
+ x2="1.9364917"
+ y2="-11.61895"
+ gradientTransform="scale(1.2909944,0.77459667)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top_center"
+ id="linearGradient38881"
+ x1="-1.4142137"
+ y1="-12.020815"
+ x2="1.4142137"
+ y2="-12.020815"
+ gradientTransform="scale(1.4142136,0.70710678)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top"
+ id="linearGradient38883"
+ x1="-2.236068"
+ y1="5.0311527"
+ x2="2.236068"
+ y2="5.0311527"
+ gradientTransform="scale(1.118034,0.89442719)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top_center"
+ id="linearGradient38885"
+ x1="-1.8708287"
+ y1="4.2761798"
+ x2="1.8708287"
+ y2="4.2761798"
+ gradientTransform="scale(1.069045,0.93541435)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top"
+ id="linearGradient38887"
+ x1="-1.0473186"
+ y1="10.741717"
+ x2="1.0473186"
+ y2="10.741717"
+ gradientTransform="scale(2.3870482,0.41892744)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ osb:paint="solid"
+ id="linearGradient4972">
+ <stop
+ id="stop4974"
+ offset="0"
+ style="stop-color:#d5d211;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ osb:paint="solid"
+ id="linearGradient4964">
+ <stop
+ id="stop4966"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2780">
+ <stop
+ offset="0"
+ style="stop-color:#ffffff"
+ id="stop2782" />
+ <stop
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0"
+ id="stop2784" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2834">
+ <stop
+ offset="0"
+ style="stop-color:#e59a00"
+ id="stop2836" />
+ <stop
+ offset="1"
+ style="stop-color:#faff7d;stop-opacity:.18367"
+ id="stop2838" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3057">
+ <stop
+ offset="0"
+ style="stop-color:#282828"
+ id="stop3059" />
+ <stop
+ offset="1"
+ style="stop-color:#282828;stop-opacity:0"
+ id="stop3061" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2713">
+ <stop
+ offset="0"
+ style="stop-color:#000000"
+ id="stop2715" />
+ <stop
+ offset="1"
+ style="stop-color:#ffffff"
+ id="stop2717" />
+ </linearGradient>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Greyscale"
+ id="filter40084">
+ <feColorMatrix
+ values="0.21 0.72 0.072 -0.3 0 0.21 0.72 0.072 -0.3 0 0.21 0.72 0.072 -0.3 0 0 0 0 1 0 "
+ id="feColorMatrix40082"
+ result="fbSourceGraphic" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix40328" />
+ <feColorMatrix
+ id="feColorMatrix40330"
+ values="0.21 0.72 0.072 0.3 0 0.21 0.72 0.072 0.3 0 0.21 0.72 0.072 0.3 0 0 0 0 1 0 "
+ in="fbSourceGraphic" />
+ </filter>
+ <filter
+ id="filter5183"
+ inkscape:label="Greyscale"
+ style="color-interpolation-filters:sRGB;">
+ <feColorMatrix
+ result="fbSourceGraphic"
+ id="feColorMatrix5181"
+ values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0" />
+ <feColorMatrix
+ id="feColorMatrix5185"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ in="fbSourceGraphic"
+ result="fbSourceGraphicAlpha" />
+ <feColorMatrix
+ result="fbSourceGraphic"
+ in="fbSourceGraphic"
+ values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"
+ id="feColorMatrix5187" />
+ <feColorMatrix
+ id="feColorMatrix5299"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ in="fbSourceGraphic"
+ result="fbSourceGraphicAlpha" />
+ <feColorMatrix
+ in="fbSourceGraphic"
+ values="0.21 0.72 0.072 -0.3 0 0.21 0.72 0.072 -0.3 0 0.21 0.72 0.072 -0.3 0 0 0 0 1 0 "
+ id="feColorMatrix5301" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1199"
+ inkscape:window-height="731"
+ id="namedview38328"
+ showgrid="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:zoom="14.75"
+ inkscape:cx="6.2137977"
+ inkscape:cy="8.1121029"
+ inkscape:window-x="46"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="layer5" />
+ <title
+ id="title38274">Highlight</title>
+ <g
+ inkscape:groupmode="layer"
+ id="layer5"
+ inkscape:label="Layer 1"
+ style="display:inline">
+ <g
+ id="add"
+ transform="matrix(1.3457681,0,0,1.3457681,8.0886149,9.9113851)"
+ style="filter:url(#filter40084)">
+ <defs
+ id="defs38291">
+ <linearGradient
+ id="top"
+ x1="0"
+ y1="0"
+ x2="1"
+ y2="0">
+ <stop
+ offset="0%"
+ style="stop-color:#e4ba2f;"
+ id="stop38276" />
+ <stop
+ offset="100%"
+ style="stop-color:#c36a00;"
+ id="stop38278" />
+ </linearGradient>
+ <linearGradient
+ id="top_center"
+ x1="0"
+ y1="0"
+ x2="1"
+ y2="0">
+ <stop
+ offset="0%"
+ style="stop-color:#f7e1c9;"
+ id="stop38281" />
+ <stop
+ offset="100%"
+ style="stop-color:#f4cfa5;"
+ id="stop38283" />
+ </linearGradient>
+ <linearGradient
+ id="hold"
+ x1="-3.3561783"
+ y1="-3.8734536"
+ x2="3.3561783"
+ y2="-3.8734536"
+ gradientTransform="scale(0.59591592,1.6780891)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0%"
+ style="stop-color:#f8edc3;"
+ id="stop38286" />
+ <stop
+ offset="100%"
+ style="stop-color:#f4d644;"
+ id="stop38288" />
+ </linearGradient>
+ </defs>
+ <g
+ transform="rotate(45)"
+ id="g38295">
+ <rect
+ width="5"
+ height="12.05"
+ transform="translate(-2.5,-7.5)"
+ style="fill:url(#linearGradient38877)"
+ id="rect38293"
+ x="0"
+ y="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38303">
+ <ellipse
+ cx="0"
+ cy="-7.5"
+ rx="2.5"
+ ry="1.5"
+ style="fill:url(#linearGradient38879)"
+ id="ellipse38297" />
+ <ellipse
+ cx="0"
+ cy="-7.5"
+ rx="2"
+ ry="1"
+ style="fill:url(#linearGradient38881)"
+ id="ellipse38299" />
+ <ellipse
+ cx="0"
+ cy="-7.5"
+ rx="0.75"
+ ry="0.34999999"
+ style="fill:#3d2c0c"
+ id="ellipse38301" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38307">
+ <path
+ d="m -2.5,4.5 2.5,4 2.5,-4 z"
+ style="fill:url(#linearGradient38883)"
+ id="path38305"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38311">
+ <path
+ d="M -2,4 0,7.5 2,4 Z"
+ style="fill:url(#linearGradient38885)"
+ id="path38309"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38315">
+ <path
+ d="m -2.5,4.5 a 4,4 0 0 0 5,0 z"
+ style="fill:url(#linearGradient38887)"
+ id="path38313"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38319">
+ <path
+ d="M -2,4 A 3,3 0 0 0 2,4 V -6.5 a 3.8,3.8 0 0 1 -4,0 z"
+ style="fill:url(#hold)"
+ id="path38317"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38323">
+ <path
+ d="M -1,7 0,8.5 1,7 a 2,2 0 0 1 -2,0 z"
+ style="fill:#3d2c0c"
+ id="path38321"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+ </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-mac/sign-inactive-18.svg
@@ -0,0 +1,383 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ version="1.1"
+ id="svg38326"
+ sodipodi:docname="sign-inactive.svg"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06">
+ <metadata
+ id="metadata38332">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title>Highlight</dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs38330">
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top"
+ id="linearGradient38877"
+ x1="0"
+ y1="0"
+ x2="7.7620873"
+ y2="0"
+ gradientTransform="scale(0.64415662,1.5524175)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top"
+ id="linearGradient38879"
+ x1="-1.9364917"
+ y1="-11.61895"
+ x2="1.9364917"
+ y2="-11.61895"
+ gradientTransform="scale(1.2909944,0.77459667)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top_center"
+ id="linearGradient38881"
+ x1="-1.4142137"
+ y1="-12.020815"
+ x2="1.4142137"
+ y2="-12.020815"
+ gradientTransform="scale(1.4142136,0.70710678)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top"
+ id="linearGradient38883"
+ x1="-2.236068"
+ y1="5.0311527"
+ x2="2.236068"
+ y2="5.0311527"
+ gradientTransform="scale(1.118034,0.89442719)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top_center"
+ id="linearGradient38885"
+ x1="-1.8708287"
+ y1="4.2761798"
+ x2="1.8708287"
+ y2="4.2761798"
+ gradientTransform="scale(1.069045,0.93541435)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top"
+ id="linearGradient38887"
+ x1="-1.0473186"
+ y1="10.741717"
+ x2="1.0473186"
+ y2="10.741717"
+ gradientTransform="scale(2.3870482,0.41892744)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ osb:paint="solid"
+ id="linearGradient4972">
+ <stop
+ id="stop4974"
+ offset="0"
+ style="stop-color:#d5d211;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ osb:paint="solid"
+ id="linearGradient4964">
+ <stop
+ id="stop4966"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2780">
+ <stop
+ offset="0"
+ style="stop-color:#ffffff"
+ id="stop2782" />
+ <stop
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0"
+ id="stop2784" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2834">
+ <stop
+ offset="0"
+ style="stop-color:#e59a00"
+ id="stop2836" />
+ <stop
+ offset="1"
+ style="stop-color:#faff7d;stop-opacity:.18367"
+ id="stop2838" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3057">
+ <stop
+ offset="0"
+ style="stop-color:#282828"
+ id="stop3059" />
+ <stop
+ offset="1"
+ style="stop-color:#282828;stop-opacity:0"
+ id="stop3061" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2713">
+ <stop
+ offset="0"
+ style="stop-color:#000000"
+ id="stop2715" />
+ <stop
+ offset="1"
+ style="stop-color:#ffffff"
+ id="stop2717" />
+ </linearGradient>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Greyscale"
+ id="filter40084">
+ <feColorMatrix
+ values="0.21 0.72 0.072 -0.3 0 0.21 0.72 0.072 -0.3 0 0.21 0.72 0.072 -0.3 0 0 0 0 1 0 "
+ id="feColorMatrix40082" />
+ </filter>
+ <filter
+ id="filter5183"
+ inkscape:label="Greyscale"
+ style="color-interpolation-filters:sRGB;">
+ <feColorMatrix
+ result="fbSourceGraphic"
+ id="feColorMatrix5181"
+ values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0" />
+ <feColorMatrix
+ id="feColorMatrix5185"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ in="fbSourceGraphic"
+ result="fbSourceGraphicAlpha" />
+ <feColorMatrix
+ result="fbSourceGraphic"
+ in="fbSourceGraphic"
+ values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"
+ id="feColorMatrix5187" />
+ <feColorMatrix
+ id="feColorMatrix5299"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ in="fbSourceGraphic"
+ result="fbSourceGraphicAlpha" />
+ <feColorMatrix
+ in="fbSourceGraphic"
+ values="0.21 0.72 0.072 -0.3 0 0.21 0.72 0.072 -0.3 0 0.21 0.72 0.072 -0.3 0 0 0 0 1 0 "
+ id="feColorMatrix5301" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1199"
+ inkscape:window-height="731"
+ id="namedview38328"
+ showgrid="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:zoom="14.75"
+ inkscape:cx="6.2137977"
+ inkscape:cy="8.1121029"
+ inkscape:window-x="46"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="layer6" />
+ <title
+ id="title38274">Highlight</title>
+ <g
+ inkscape:groupmode="layer"
+ id="layer5"
+ inkscape:label="Layer 1"
+ style="display:inline"
+ sodipodi:insensitive="true">
+ <g
+ id="add"
+ transform="matrix(1.3457681,0,0,1.3457681,8.0886149,9.9113851)"
+ style="filter:url(#filter40084)">
+ <defs
+ id="defs38291">
+ <linearGradient
+ id="top"
+ x1="0"
+ y1="0"
+ x2="1"
+ y2="0">
+ <stop
+ offset="0%"
+ style="stop-color:#e4ba2f;"
+ id="stop38276" />
+ <stop
+ offset="100%"
+ style="stop-color:#c36a00;"
+ id="stop38278" />
+ </linearGradient>
+ <linearGradient
+ id="top_center"
+ x1="0"
+ y1="0"
+ x2="1"
+ y2="0">
+ <stop
+ offset="0%"
+ style="stop-color:#f7e1c9;"
+ id="stop38281" />
+ <stop
+ offset="100%"
+ style="stop-color:#f4cfa5;"
+ id="stop38283" />
+ </linearGradient>
+ <linearGradient
+ id="hold"
+ x1="-3.3561783"
+ y1="-3.8734536"
+ x2="3.3561783"
+ y2="-3.8734536"
+ gradientTransform="scale(0.59591592,1.6780891)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0%"
+ style="stop-color:#f8edc3;"
+ id="stop38286" />
+ <stop
+ offset="100%"
+ style="stop-color:#f4d644;"
+ id="stop38288" />
+ </linearGradient>
+ </defs>
+ <g
+ transform="rotate(45)"
+ id="g38295">
+ <rect
+ width="5"
+ height="12.05"
+ transform="translate(-2.5,-7.5)"
+ style="fill:url(#linearGradient38877)"
+ id="rect38293"
+ x="0"
+ y="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38303">
+ <ellipse
+ cx="0"
+ cy="-7.5"
+ rx="2.5"
+ ry="1.5"
+ style="fill:url(#linearGradient38879)"
+ id="ellipse38297" />
+ <ellipse
+ cx="0"
+ cy="-7.5"
+ rx="2"
+ ry="1"
+ style="fill:url(#linearGradient38881)"
+ id="ellipse38299" />
+ <ellipse
+ cx="0"
+ cy="-7.5"
+ rx="0.75"
+ ry="0.34999999"
+ style="fill:#3d2c0c"
+ id="ellipse38301" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38307">
+ <path
+ d="m -2.5,4.5 2.5,4 2.5,-4 z"
+ style="fill:url(#linearGradient38883)"
+ id="path38305"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38311">
+ <path
+ d="M -2,4 0,7.5 2,4 Z"
+ style="fill:url(#linearGradient38885)"
+ id="path38309"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38315">
+ <path
+ d="m -2.5,4.5 a 4,4 0 0 0 5,0 z"
+ style="fill:url(#linearGradient38887)"
+ id="path38313"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38319">
+ <path
+ d="M -2,4 A 3,3 0 0 0 2,4 V -6.5 a 3.8,3.8 0 0 1 -4,0 z"
+ style="fill:url(#hold)"
+ id="path38317"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38323">
+ <path
+ d="M -1,7 0,8.5 1,7 a 2,2 0 0 1 -2,0 z"
+ style="fill:#3d2c0c"
+ id="path38321"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer6"
+ inkscape:label="Layer 2"
+ style="display:inline"
+ sodipodi:insensitive="true">
+ <g
+ id="layer3"
+ inkscape:label="Layer 4"
+ transform="matrix(1.0666804,0,0,1.0666446,0.0428691,-0.00298246)">
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:2.24979424;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 15.667684,15.742702 c -3.40903,-2.920537 -4.694046,-5.522759 -4.694046,-5.522759"
+ id="path4586"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:2.15561461;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 15.747607,10.026563 -5.09709,5.759107"
+ id="path5174"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ </g>
+ </g>
+</svg>
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..76e7e9ec79177e88c197e5874a8f4cc80ee69923
GIT binary patch
literal 3461
zc$@)+4SMp4P)<h;3K|Lk000e1NJLTq0015U0015c1^@s6J20-I000W3X+uL$Nkc;*
zP;zf(X>4Tx07%EJmUmDTc^1ci-IEhA3|YdElOUM^B*!5Ll0j648Nv{TabN%u6SBAh
zD~gCDWknZ36kL@cYd{PbSOgR>?;wkcaxMl$MdcbehP%4jKd$Ot*X#cDtJmLt9e!0`
z0LXnTK0g_j0w9H#E(r8=p@&CA(s6$P2-!%$4j8f6X?*8^0AKL8@o@<0MdGf}oEG@s
zg35_aV*>yMARNO<W2Yc|7Rh!tUyu#}YEDlxGo3%jdk~fpAOj+NbdKW}xP6Xe7Px1=
zCpgd*VG#g$GK(dM2Y@tz)ae=QctnRxM%aYM;qnk(iLe7ZfyF^M2jS%@DXDW@kFZ|M
zzv;yPhh~gelf{Y`^TM;t2i}dF#!qH#1pn_yNfs{NLjy^2g1{>fIU-*Ws!vMw5i>7l
zmH#4hIrIJVoFMcHS!8yaYvdwxSZ+RxEKCY<USyVF=~`|&GkB2&sexks*fjSbv3@Mm
zS3E=VDlx}#J(!D}oe&(h$Qj(wRg0XK6y&qi<0`HTgn{Cj;{+b!XG}?3nvKm`njt+Q
z*lTIFaPbVBST}cZofjhR=cl`fHTlT_;{MoVPjNjhBS?I0I`V@paw5w+U`Z!H{C+?X
z@B=Qu6xaX@U;@I@W3$rdhILKlZxnFj6VmC<$ZCnDGkI*|<#aO>Q%eBm*51OWeSd#`
zO+j+_r5Ybe8!GY-b}iLntO01N1VE9xRMV;fKoWUht&wb@AY-A1p4(vn5l8|Vpa@if
zCeQ;$zzkR+YupLA18?9Df<XjW2iPD1qyPcP1UX<k$OnaB57-YXK@B(p>cL5H8nl7)
z;4-)lZi7BB2p$0u7z3}tB=`tsAPB-i6o>{XLTZpUWC)o-49Ed;hkT(xC<2Ox;vpU+
zgmR!AP$5(TRYHfM2IyDl9CR7F3H3t{pb=;sdIx=hQ7{>nfmL7~cm>RWonaq17+wp<
z!yDjiI1k<pm&3L23Ah!$4Bv(aVG;Zqo<ac>2_=V8M;W0QC|8s}DiRfk5}>xA3Q%RJ
zT2vFN1J#YXixQzGP@mCQG!0Eh8=>vcUg!`s8@&O&1zm)$MAxHR(O1!T(Ie<d^mhye
zql_`c*kYKNa7;WV6O)fA!_;9~FxN1Hm@&*pEEX$=)x}z4y|5A3MC>MP5%wUq8QY2N
z$BtsBa5$VI&Jbsh^T)Ap>9~Ac1?~jy5^exDhWmsk;nnbFcn^Feo`=uHm*J1&FX9LA
zFYq%2DS|e^mf%m|5H=Eu2}cPXggb;W!VHm0)Fs*zgNcd6?Zk3o6Y(1HG4VZ#MA9JH
zkXDnpq-~^f(kapn(g^7@S(<D>b|puVh2-7jW8_Zq1M+(cg`z`oqC`^CDZ42Rlq-~{
zlur^g31bPS1V>`4M3qFV#DK&bNrI%dq_gBY$!y6o$up97BqyW@QaVzuQY@)0QdLst
zq=u!Ys5Gi6)t{O|EutQ$-lV>i#!2f)yGwJV^Q4bRUy&ZAp=g>kR~nnPgLag5jW#BO
zmC==9$|TAZ%AAzxm6?>4mbH)#mCcf^lD!}+l0(Vq$T8)T<%;D_%MHnWkynv-mXDR+
zCEp}JAV00JOu<PZR-r(lSz%D&iy~doT`@^<uVS0x6D72gfzoQFOr=9g*OVrf$t<&5
z#$Hyi?DVpS%CNG5a-i}i<s-^H%2O(;DxNBQl?s(BDif-5s!pnjs->zIRmat6Y7S~#
zwNkZ9YOm<BbZ2@py_|lPKB=y(?xikNKdj!XKBJ+l5u%Z+(X8=Alc>qijMFUD?9!an
zQq%I&%F$}ndZ<m%W@sm9AJFdBp4QRT3DeoB)28!MS3#Glo2}cZ`&f^n=cu<quU7A#
zK31QhpQL|Kzs~?Rurx?8s4}=?2pd`%at#j}_AkdQw^`0xUb}qQh-~C+lws6pG-51g
z>}#BB+-Ce{h1QD56?<27ulR0aX_9Pm)Z~$=w5hjgu4#wqJ2L|_wpo?gJ#&h=hxu0X
zcJsFuh8D3Fhb-<}(k%TfcUpE?ezUT+5?D1^jazG3M_X504>M#K0gNKX4I7M&i_KP>
z^ERJtt!&e6Puu=wXJnUTciisfO5K&QE03-mwb!s`*&ntSInW)V9e#EYIjTFd9BUn)
zIcYj^oa&rjIO{tnI-hWU<6`0>aA|RwcC~fg?Aqyua`SX6aO-uKb`No{bbsoh;SulA
z=rQSO>AA_X(+lI}<F(gon5n{KGaHzb-qzlmy|4O^eS&-{eV+Lm_zHYG{9r$CzkPm>
zS81=}t!ncJ{!IUU{!apQ12zQwwi>%SV0G2%=YghyIf30lw4n7t$Adlwy95^pKMK(c
z5r%YyN`^*-HiS-xxrLR4iNaTeZw|j5p%lT5Xph82hDO#!POWiYvwzK4lx0+Y)X-Yp
zwOMO#u2Wi<yzat!$@Q%DXQDCDVbRB<zq0&UwXCTaW=wU=B-@pJfc=`|#3|*B$J)o1
z#J-5Lk1L6L8E+rIFMd41F`+DBg6qny<i1VxN~}qoPV!GWmNc6jmfW0zPl--B$CKtI
z@vf$-re>w~^9}j=e9;Em4f{7t3VZ~0X;9kQwDxov#N_k{^@KkNM=~5Tsxm%hhGw40
zqGs{3dNvwt+_mvVwnz3ao6wuso4Rt;bGGM<Y<Av!cnh>8W=q#r&8>M`$F_NHtKUxC
zp0xe<T$9|A+z&g#c68*a<Za6v{lW8x#(ar<LH_VgyPY*ZqJQN6*t^SoSH-UH1)PFg
zg(ih%g<p%<MYn!3{i*z?+1+uwdyB1#5ADJ3;q4jP>$tbRgj$kaGP=)iUu!A7w4n6E
z{`LEBmRXe593UPL9uSrLmbX`ERqUzwTA5HeSmjdHT&-Nat9tq%=itC0r$bFODm8^Q
zUw-EPeE+cL;nrH6+Oi|qBN<1Y9}PX){R`ulhB~FX!n$wAc*jKbf%R7#tQ#7RD<3az
zgd2s8<0qm{^qq7&+1_N-bhufrx$qP?B|P=&*XUn|PWznhI>R{AbXNOpbqlSfpcQJ}
z*gDyk&?ah+XzxGgeXgs+uA}8Q<KK>*S3h5MLH0uNMdHQ0i?f%qFHLpwJ0~v3UmopR
z-}UH9_?3HCS6}VD=5y`Fb&u;;x}Cc(-LSuL{-*8CbGI0`+HPCjZtbz`Y5Cpq_m*C(
z-qt(TciQ`G`a1eo_Fo)u9JqYf?e6t^%zL*7R}J<Lg$&&vjv5x-=iGn!Ao;=DhZzrN
z9&LM!d0g;B>PgvC<)^iO=>O3yvJzbwaU1D*7WC}VD0}qvpJ{)79m{)8d0zHH?M1^&
zvzO<`J;wW9t$8)}n*VxcBL5%Me^kHGdvo?Lr@wk8!zahy3f|7XD|)Z^{@4eL4_#9M
zQ%^spe4Lps_@wx${xjoq_m{9QFJ>~o;=Y!D)Bo1--RJw`*_7E?K1;xww*n|6;kY;e
z-W>yg5&;0M4*;UXf;XNIXu;3V6Zkt`@XzyM1;CL`0D_$W@I{Ul+W^o(QW7~2aGI~f
z=H}x3SER+6n=NRO+7N3}Haq(s@q{>i0KN^+&d#*W&VH*yhJ6S?=f?$4J{P`50ATlr
zGHl#$@QVJ+8~+o2YXM}Bs!>h=000JJOGiWi{{a60|De66lK=n!32;bRa{vGf6951U
z69E94oEQKA00(qQO+^Ra3l0nh9fb98LjV8*V@X6oR7l6YmoaGKP#ng;XHFcd6|@}9
zBISm5=psEgT@(skI^?DfX6kT)aO*+0l7cQBg2Nq$V_Z)c5nM`3p%mPTR-xDy2OT14
zC@EUgcatISl-_Zd<Qu;{_~rlq-pl)ksH%#;(PT0~wOXZ@mzQX@T6lPPz<4}HzuzbL
z2fh2#g27<$GoQ~3l}ZIqPfxH*Z-P%xPiL|$4|QFK-C7EsPNxtAfnHx<1HgMYAkQ*r
zJRYAz8TouZ92^{=R4Sp<>3mUDmDpDC?(QzVzrSZZLI^}rgd|C1Wo5<wRv#T5(Pp#B
zEb#mNP!t7;M1t7aYBE1gy<V@ev$OM&Qp!LtE-uQENaT}a8E3QEO)HcT0!2~a1k?oU
zx{j->tAO=^D2leXcL6NRGPPFg^Z6i2l4DR4T&vX>QxF8$wR_(LKR!M(=KlUZoR*of
zRk2v?vnyb0z5oyohkv*N_Ika{*>yu600;(yjHzjwJK&9t4aU5=xoNlpjz*)5d3JWT
z=?XZROfqJ@UJuM>Gv~pnREmT`A=7+$c|oyQq^|7h!^1<<q?DpwucO=TW*h|rKqiwR
zYin!f%|t0hp-}kt{QT@JI2a5fm&+}Tw6ZMI;c%FDbmkHSfuz%Ev+DT_RxX#n-rnBY
zn)$-ab$onG48x%9cALrk?(Pn{uG6ipEn>&lZDGpfI1VQ#CuDDLkEwi~`)D-!ZWxAr
zGuJXT8V!1WevZ}ERq#BI_4Re|JP)4diO1t{?oT2Xi;?Z^ZM0e~I)B06Z!$+Jw(9lu
n6`H1f0DwRs(B(Mpzreo$fyw&*J0h@i00000NkvXXu0mjf3onty
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..7fe7fb542f47344bf71b319f326f617b43fc56b0
GIT binary patch
literal 235
zc%17D@N?(olHy`uVBq!ia0vp^oFL4?3?zm2ODh8@mUKs7M+Sy#H?H_c7y<c`C9V-A
z!TD(=<%vb942~)JNvR5+xryniL8*x;m4zo$ZGei@1AIbUV`C0M5K!Rc$&**FUVZT3
z!HX9!-o1PG>C>klKYslE`}hC<|5-uYzkn*3OM?7@Q`67zu<UyW<XU^WIEHY@CMN|2
z1tle1irShrl}Y&U-aYI*4xi*F{MrBOdllQVdM}?Mhfjb1mpcfmDp#^CGq3*k<|hNg
X?~|gl_b<`_xt_t()z4*}Q$iB}{##qC
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..df66d771c347df2fa658659e0c0882ba2fe0d39d
GIT binary patch
literal 232
zc%17D@N?(olHy`uVBq!ia0vp^oFL4?3?zm2ODh8@mUKs7M+Sy#H?H_c7y<c`C9V-A
z!TD(=<%vb942~)JNvR5+xryniL8*x;m4zo$ZGei@1AIbUV`C0M5K!Rc$&**FUVZT3
z!HX9!-o1PG>C>klKYslE`}hC<|5-uYzkn*3OM?7@Q`67zu<UyW<XU*TIEHXUCnp63
z1tle1irShrl}Y&U-fDh{1yBB8U47ly;BRoyB<4x)@9$^s($G9<Y_Rv{=JfOI42%_`
Vv){atuLGLF;OXk;vd$@?2>|rJS}*_r
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..561262f247bc25705b42eda6071f96d9aaff1024
GIT binary patch
literal 3833
zc$@+G4hHdwP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV000aUX+uL$Nkc;*
zP;zf(X>4Tx07%EJmv>at$ri`wS5gQGEf4`Ip*KN#mEJqj!A3{|gc3qRP_d(n3a(v5
zu%fFVimnBawPL}72-xd_h>Ef<7Hp`zNdo9|&;Iewd2`PE^2wch?|f%|GruzffTBH1
zAjn3Q0LbCz3!?*ln2AZrOwuDj1qdJmHDJPGiv-?LQDNX;%lli<7nbXml3d__E6N<M
zhz$S|1v!-?V&_0U0syYb76|hJAhbif!OVPtgx^6{6+(dk5Xllwm$8<FQ)O%|^@)x4
zh3p3anaW}b(*aPB<YyMJ(_tP30c2}FhsTG!9kK^IgT;Z|1$jbFPA+6cDrD2tAM&LC
zSI*S2oGe!Q7|X^YEo48QNRZ8%4gTLHCtEz)hd!*-3}H~TWXy2hs<U!KB@DGA$N8xd
zk&sm&U*vJ5{t~{IAr6Wg@$YAgeBqn{1^_7Fu>3+H!?{Em;;cCD5w>LsrLoW-jo{@6
z$BuBSFgJQ6KU%;Q`NxcM1~)itB<C`IcEl)eP2&XwLxw92JyMtvn;_GPo-5$RM?hA9
z+%C$B2_5l&RG8r#G3p}}M@uvtLVlMf42YKbVCp%d(Q#o`Y?f4mI%Kc>jMyMqESAU>
zC5DY^<8b}_Wg0LJmmfEx8H20KCwe5dSdbkx;*V|RW(P*g<{3LEDu@}4?aYS@Og0bL
z<4jgaluSSNUXULZE7OMKAPo2dKfnZHSW`hR$OOE${)T?|Pu3CuSU?EU0T)<~SVkif
zq%C~-i~)t<FGyS<8jJIhwsJuMw0|A5%AS@WP1;%@jmQE|A?M(lxCicrhry>8thTr_
z?lKz3R2=P<=uh+`1`z{@W@9?oP<1w}Lcsf{?RzBQDWQY#m~fNOMYsz>VJjDoS)!0X
zHnMo|M4Dyf{|sJ{@F(R=5DvOvT)@caQekx8s1I%oRXgB5xI5Gjwc`w26IkN*a3tQi
zCmfptwEn12Jf`CNT>jjjMBk5N9I-2yD>#g(NFCGfJLdIs7T+J{hy#B^fA++dVTZ96
z><o4ZJB~E~CUy#I#9FZv5*}L#Poyhhw6|!fwk(*3H|k?uYu#u4<sZGXMidC8?;GTE
zXXQ&agm123wvd;ek<avoUt}&bn9sJFz_hitu?Il%eV2Vc-ZG@$ZG>_8yDjfD04^oa
znEu_yG63M%Qvm4izuOGn!0ojffD`R(v9Lg9!zB*_pa4a<Gc<q>FaV}-Ot!!gxB)NV
z4?;jBhy_VtI-K<kkONiE1arY6Pz*}JYOn!RfNfwWs0RDNA<zJhgEQa)xB}Y2O>iHs
zjb88)41o7w2tg1MLPO|?2BM1?Ar^=&;*5A8{zw=SjU*u{NIJqt#K>G^F;a@GMJkXT
zNDcBkatt|xTtYgKyGS?EhrB^PqZmp>RZ(r!7@deZquyvJ8jDUt)6qP%5M6?<M9a}C
zbRSxeo<XmmH_<NiB|3-!OdexkdYC2VjQL`bSTdG|39<QD308(xVRcv&eAm~pF03E>
zgcET(&crR@`V7M3a5kQY&&OBb6?ipXkDtS@<K6fG{*6E*Xb~(3u7qGhA|ahHlTb`3
zBkU#|C7g#ls+aJdNF*|dCPXK=H<O5u$j#4W@F#M8tMVh?eUL?UUBEJ*I8ND_;b
zPbwyDBJCrcB6X5_Ngv7bWL>f?Ie?r@=97!aW#ql&ljKhFGxCs}lAMv8t6ZcUM{c&<
zD!E;9O>)=ddgX>F$`lidJ0+HqNm)cGr_@o-Qyx&>%FD~^%e%-=mgmVYlrNV*D1TAD
zTmBPOg=$XqrB0=asVk{_sHdrSsc&gCnla6bmQ2g1t)$h^&e9&yJ}S@^tQ3M3I0_3C
zDi!J#Iu%|i$|)KtdMi#>EL1F0Jgj(4v0q6}$ymu(iKR4OsZ!~f(ru+dWxBGBa-?#O
z@(Sg>%9oU%tB_QTRs2;rDoa#$s+?8np<{Fdx-XqgUrgUcKTq#fC90aJ2CHVOma6Vo
zZBrdkQ&F>5i&vYaR;hMEt&4#%j2Xd<Y{n|aA;vAn=W*KOyvK3JEgx4mu5;W6_3`Rn
z>Rj~_^#kfR)jw-6HT*TQG}dS|Xgt)!HO)0AYtGc%s(D_sf4u5=xAE-pCF2i`zo&(2
znQKLB&C%Ma)v7h9t)m^NEzsVieO9|)hoR%8lc}>_=cG=bE?w6{m#4d4_mu7nrW(_m
znawO`o?{N^Y3T*&iS>5qwdoD%o9M^s7wI3+e`r84a5hLYSZ~m5Fkq-_7;ZS%u-5RN
zk(`mUQHIe*qZXqN#wNxI#!HPGjGvpRn*^H_n$(&+Fr}G#n&z2SncgrXnmL<gnQbv^
zH%HAK%rnd@&97OY7LFD?i!Bx%69^MrCh#XzO}K4Iwe+?uu&lN0wqjU?Sru6|SiPEP
zI5BzRnu#qFzgat2XIt;GzHg&q6Jk?j(`56;*4&nDTWNd4j%F8Nx4^E^?zO#zJ=cD#
z{T&CoL%73Ihh~Sbj!uq3$2!M8Cqt(cr_D~comHKqoL4wsbRoF-xGZov?()gi(KX-o
zkn7)WmTp;Yd)=N-GMU7kv};n2yS_WieTRFO2h$_PW4lL}r=BOvv&!?amw^|@Yq!@k
zZ!_;q?|t5{e5`$hK1Y2%_`3Se^F8f{`33rw__g_~_{aHI_&*BJ4@eKF4HyV?2%Hmm
zI*1Sy8nh<pcCdCZJGdrzAjBzTUdXvnYG_PoMd;%&^DtppQ#cwP6230{L4;8RKcXQL
zL<UE$i+mVm5|tPA$7JH<sL7R+pGMn6&y8-0p~uXK*&Fjd)+csV?EN^?IB{Haykh*+
z_?q|+34RG{6S@*7CeBN|lBAi$OKMCeCC4Z4P9B`%KV`#|-l+~#i>Kb2W-@Kov`f=9
zr)N(;F+*_%YsTReLP|o)-jpw_NLCeVFf}-JbLs%wm%WkwlH<i$&*|g3bJud8rMaiA
zO?#g1p1v-<FT*pVETf<2%d6nM&J4=jmiZwoGHXxPaCSoWfgEy9O3pF93O|c~E>|aa
zR&IyDLQpK|%5%-zkT)O<748-xqG_VWd{ubVX%(A_e-U>Vcob9?e4H6S^YAR?S^Qb8
zv(0BOpZ%;bpzyaj_#F0}=DB)v7tQUS=RI%7d}My={N@D)3zjVCT^P8qdJ$z&)}pIL
z)<tWJ-Yrg8+_Xe{$-*T)zXbkLTdYtlEWW+eZRxgU_%hzIw&ixqH!c5G!YR32YF%1Z
z`eg-s#pPdZel7oXcxBqkwpEU+wyq|w=C8iB#&b>eTIIEcYkSs(ud83rTwk*O-G&((
zE|uArZQDrMDBjpr9#-DC$#B!^O<yW9DsEQ#R36x@wR!pG4_i1}I=6alt=p!(t#sSx
z?Y!-Gb_DLIuQIMG+ezG6u=DAz_+2f(IsaC(TXT2m?yr0Jd%CKltIyRq)zs|O+PkV2
ztrgey?VGx<eZSxS#yZQo9S0Z(N)LjA;)5@KPx<}Uq0mFkhn)}C9Wgqx`6&HpNj*|O
zyMCY{qoJ!Ysj=f&$g$=ox2B_iSpBi*xZd%~6KW?`our&xa&q`o;i<vXg46wH($DlX
z&uG4XHu3C@bCb`toew>K@j}3bvn}2&r!Kl*Jbuab(y`0Vmm97)Ua4<&Xgzw>;p)*g
z$F}-wPS+aSUD}&ECUu<b^z1xy-S7H^8^JfO+>E%{aVzfDo!e7ycirLKd44zh?(2I6
z_lE8-d_Z_m@=)nv*(0q-RezfOd7#U&>qNI-ckAPr$M<{KJujb#o_y(D@|5<p?3wPf
zn&-C9kM{-iwZEA1qW7iX<xqd|U&?=Ner5XV$lqRnw+<u@^u89p9)7dpt;X9u@9f_-
z4@M0>dY|)t=tIdzjgQrzoIkaEPWb$6XyzBvm-4S>Uz@&#etR&SGdwI{30cyk00t|X
zmIlC^Jpj;>;MoD*<0uNU`<JvJvOA-cz`wEVjwUUR0POJsAZ{T5VepKy6;>n2RQM}7
ze#d$N)Xr|KjucUvovkbv72aY9ABTtEY6C!O0^qA~cz9^b@bK4NQ0!d*PUp$)eUd_i
zXZ-EbbIw(RrT@5p{RjROYWNk`t^NQ200v@9M??Vs0RI60puMM)00009a7bBm000XU
z000XU0RWnu7ytkO2XskIMF-vt9S;N&AL>TU0007LNkl<ZILnoiUrfzm7{@=q-|rli
z$((a=bo^^M)J6UiE{fB3Hnuj)(rD6!=HHbY)?pZ%M$u-L3t<eiVP<Syl$FI2^QU7>
zG!tT{qeD(~aL(_2Tu3U-c&?uJ-S_=|_I=(5hN!McBLGTHWJ<Ks6$%~VzsnSv<lCDr
zjSq630DS${{BI9MIsy3Hlm93oYq8{Rbc>Q}MgK)tQtr;%kgCpKohFmA(&h0p&le>R
zEgivq|7Oq`UKPN-R7_bQ{-;wUZP^qFcl(wPrPcqWSLSXmX>wja`5bS-2kt!3v6@y|
zUesR^2_^P~s{c-Jx}WJ!@=O;~7D=4PE9<r;%j@=qk}%UNrmQR;thfAX-glJZ7%Rm~
z*Bqa5w$y^IMl)7_i+zM*Vv;a7^(8eoY*iw1PSdo=9}&hlw^P?}dC$9#R<biD05NQF
z3cF1M6qhP(6zqyevG%Bl7<~p2vju>t>?!e8gk7ihE8sJrNF4bcm+L3PY1dh?=nbWZ
z{c8Yi(BN(+UaX!I^{$>OVtPc(`6A|gx!as9!OnSo=Rhz+;xn_wl)}0J5yCwe)^92s
zX{kD3^wI%_v7_8dZCzI%$muZHSJ=iD-|Z~<zS9YyE2&SZZ>Txwo&Ac*;~fL$d#iOC
zH}6ZoyBI}zc8=7h7@wlAuK{Rn?Q6@D^8;I3&%7Z%e%Qchstet)f&4Zx3@cTqyZIRD
zregPwB^G~6+g3+H53@GgFiZnQ3{BI47%;BiZ^r-&3eXu2F*~d7-pkiMgiCUQfpD5G
vpfsRY0-$SxQu?6P6%-nA`Y6YEH^+Ve(rpoUN66bS00000NkvXXu0mjfGssP2
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-win/attach-active-18.svg
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ version="1.1"
+ id="svg68075"
+ sodipodi:docname="attach-active-18.svg"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06">
+ <metadata
+ id="metadata68081">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs68079">
+ <linearGradient
+ id="linearGradient69547"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#eecb20;stop-opacity:1;"
+ offset="0"
+ id="stop69545" />
+ </linearGradient>
+ </defs>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1078"
+ inkscape:window-height="717"
+ id="namedview68077"
+ showgrid="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:zoom="15"
+ inkscape:cx="1.7362319"
+ inkscape:cy="3.9196061"
+ inkscape:window-x="166"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="layer2" />
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="Layer 2"
+ style="display:inline">
+ <path
+ sodipodi:nodetypes="czzzzzzc"
+ inkscape:connector-curvature="0"
+ id="path77072"
+ d="m 6.8194899,3.6676829 c 0,0 -1.5664037,1.9124961 -4.8594892,5.5898643 C -1.3330856,12.934914 4.4611524,19.40164 7.4269247,16.137379 10.392697,12.873119 14.121702,8.6857836 16.34864,6.1186233 18.575582,3.551463 14.758628,-0.78139789 12.476236,1.7327302 10.193845,4.2468574 6.3806119,8.6385171 4.9212518,10.289521 3.4618907,11.940526 5.6504624,14.00041 6.9713487,12.525467 8.292233,11.050523 13.767037,4.9146521 13.767037,4.9146521"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#f0b305;stroke-width:1.95830035;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer1"
+ inkscape:label="Layer 1"
+ style="display:none"
+ transform="translate(-22.386738,-79.656408)"
+ sodipodi:insensitive="true">
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#fad938;stroke-width:0.80000001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 6.8194899,3.6676829 c 0,0 -1.5664037,1.9124961 -4.8594892,5.5898643 C -1.3330856,12.934914 4.4611524,19.40164 7.4269247,16.137379 10.392697,12.873119 14.121702,8.6857836 16.34864,6.1186233 18.575582,3.551463 14.758628,-0.78139789 12.476236,1.7327302 10.193845,4.2468574 6.3806119,8.6385171 4.9212518,10.289521 3.4618907,11.940526 5.6504624,14.00041 6.9713487,12.525467 8.292233,11.050523 13.767037,4.9146521 13.767037,4.9146521"
+ id="path77076"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="czzzzzzc"
+ transform="translate(22.386738,79.656408)" />
+ </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-win/attach-disabled-18.svg
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ version="1.1"
+ id="svg68075"
+ sodipodi:docname="attach-disabled-18.svg"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06">
+ <metadata
+ id="metadata68081">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs68079">
+ <linearGradient
+ id="linearGradient69547"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#eecb20;stop-opacity:1;"
+ offset="0"
+ id="stop69545" />
+ </linearGradient>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Greyscale"
+ id="filter76500">
+ <feColorMatrix
+ values="0.21 0.72 0.072 0.1 0 0.21 0.72 0.072 0.1 0 0.21 0.72 0.072 0.1 0 0 0 0 1 0 "
+ id="feColorMatrix76498" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1078"
+ inkscape:window-height="717"
+ id="namedview68077"
+ showgrid="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:zoom="15"
+ inkscape:cx="5.0884354"
+ inkscape:cy="3.9196061"
+ inkscape:window-x="115"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="layer1" />
+ <g
+ inkscape:groupmode="layer"
+ id="layer1"
+ inkscape:label="Layer 1"
+ style="display:inline"
+ transform="translate(-22.386738,-79.656408)">
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#fabe14;stroke-width:1.6147449;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;filter:url(#filter76500)"
+ d="m 29.159662,83.21089 c 0,0 -1.599889,1.953061 -4.963371,5.708428 -3.363483,3.755365 2.554619,10.359254 5.583791,7.025756 3.029172,-3.333496 6.837892,-7.609647 9.112436,-10.231258 2.274547,-2.621611 -1.624002,-7.046374 -3.955184,-4.47892 -2.331183,2.567453 -6.225932,7.052262 -7.716489,8.738285 -1.490558,1.686023 0.744799,3.789598 2.093922,2.283371 1.349121,-1.506228 6.940961,-7.772244 6.940961,-7.772244"
+ id="path68084"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="czzzzzzc" />
+ </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-win/attach-inactive-18.svg
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ version="1.1"
+ id="svg68075"
+ sodipodi:docname="attach-inactive-18.svg"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06">
+ <metadata
+ id="metadata68081">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs68079">
+ <linearGradient
+ id="linearGradient69547"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#eecb20;stop-opacity:1;"
+ offset="0"
+ id="stop69545" />
+ </linearGradient>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Greyscale"
+ id="filter74568">
+ <feColorMatrix
+ values="0.21 0.72 0.072 -0.2 0 0.21 0.72 0.072 -0.2 0 0.21 0.72 0.072 -0.2 0 0 0 0 1 0 "
+ id="feColorMatrix74566" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1078"
+ inkscape:window-height="717"
+ id="namedview68077"
+ showgrid="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:zoom="15"
+ inkscape:cx="5.0884354"
+ inkscape:cy="3.9196061"
+ inkscape:window-x="115"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="layer1" />
+ <g
+ inkscape:groupmode="layer"
+ id="layer1"
+ inkscape:label="Layer 1"
+ style="display:inline"
+ transform="translate(-22.386738,-79.656408)">
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#fabe14;stroke-width:1.6147449;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;filter:url(#filter74568)"
+ d="m 29.159662,83.21089 c 0,0 -1.599889,1.953061 -4.963371,5.708428 -3.363483,3.755365 2.554619,10.359254 5.583791,7.025756 3.029172,-3.333496 6.837892,-7.609647 9.112436,-10.231258 2.274547,-2.621611 -1.624002,-7.046374 -3.955184,-4.47892 -2.331183,2.567453 -6.225932,7.052262 -7.716489,8.738285 -1.490558,1.686023 0.744799,3.789598 2.093922,2.283371 1.349121,-1.506228 6.940961,-7.772244 6.940961,-7.772244"
+ id="path68084"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="czzzzzzc" />
+ </g>
+</svg>
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..7895fc1dbf30745daf0717e1ee13d2a685658b73
GIT binary patch
literal 770
zc$@(O1O5DoP)<h;3K|Lk000e1NJLTq000;O000aK1^@s6crbl+00001b5ch_0Itp)
z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01ejw01ejxLMWSf00007bV*G`2ipM(
z5)T9C!?p7O00M$ZL_t(I%dL`INK;_|$N%5X#m&v#b-GMl&4d&$Gnzqa2DQW@>?NU#
z6;bq5j}ZkD6jTqr1W}2RL@!ZMJtS017kTMHx?tv|He+fow&^^&v$LIZdZ+}Ku7ZB>
zz5T!6j}JbAWf(ZKb}yx54<nIyEz0ug;+6pcf5DfB6*V$#sEY^3uG;s13Xr8nAJ3K?
z$|x;sHIx;R1Q$c#MOPOU@^7=X`1n-v;r2pjn#uN(*X&`))U#=B^uZcM@@VK>X_K~M
zeXG9MN`!DXM1BYsS3|9wW$W;^6ev^44Mm$hx>c1733?#woq9qnM}7-{RUFfoZy<4Q
z5DLbK=p+yR>mV$yrdlyG|4u5o+G=On^mJqura|=Hhzp@{51l+F4DpLm8k8~@M8QH=
zyc&QSbVerB1tmLyu4Uzrc*ru3XmCP^2fa1UOMKs=z{r(K2ctDN&@2aqx)I{sTY%O=
z!59I?io{&d|0j9GHy{MM-8E&WB7Fd`5E!{u<zTcXx24LZ10^6jISAFtI)KDLC?kTe
zu5*0Y*Z42;g@CWE%AwF$+-9dsCyN0H-zbFNJn7{=_m^wzTb0rAd$X~L-c5x^d;5P~
zR9j1KsdG}x0f6}0svVTx;x<)o(Mj{~!DiMY*moCvz^5XVlp{$}wM2>C_I*903$MLx
z*Q&GYr>V3U(d3#D8T+uL8~|wAV18~|w_PWM9zmJijKsVI2_kF<J9z-!pJx&GJ7*3w
z?KYD5lvfVDdpa!43^gw${}Irw&{L``sPc{@F&9Ii=X?htNna(22p12x-@JUHE;rJ)
z@ginZZHfQ!4w=cw0{}?zc?h#(C-Zl?FW^7%9io^jzR{$)#sB~S07*qoM6N<$f_w^7
A_W%F@
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-win/decrypt-active-18.svg
@@ -0,0 +1,168 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ viewBox="0 0 16.874999 16.875"
+ id="svg3423"
+ version="1.1"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06"
+ sodipodi:docname="decrypt-active-18.svg"
+ inkscape:export-filename="/Users/pbr/enigmail/Logo/Enigmail Icon V3 16x16.png"
+ inkscape:export-xdpi="2.6199999"
+ inkscape:export-ydpi="2.6199999">
+ <defs
+ id="defs3425">
+ <linearGradient
+ id="linearGradient4972"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#d5d211;stop-opacity:1;"
+ offset="0"
+ id="stop4974" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4964"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4966" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2780">
+ <stop
+ id="stop2782"
+ style="stop-color:#ffffff"
+ offset="0" />
+ <stop
+ id="stop2784"
+ style="stop-color:#ffffff;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2834">
+ <stop
+ id="stop2836"
+ style="stop-color:#e59a00"
+ offset="0" />
+ <stop
+ id="stop2838"
+ style="stop-color:#faff7d;stop-opacity:.18367"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3057">
+ <stop
+ id="stop3059"
+ style="stop-color:#282828"
+ offset="0" />
+ <stop
+ id="stop3061"
+ style="stop-color:#282828;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2713">
+ <stop
+ id="stop2715"
+ style="stop-color:#000000"
+ offset="0" />
+ <stop
+ id="stop2717"
+ style="stop-color:#ffffff"
+ offset="1" />
+ </linearGradient>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="20"
+ inkscape:cx="-3.2745776"
+ inkscape:cy="8.1578366"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer2"
+ showgrid="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1163"
+ inkscape:window-height="728"
+ inkscape:window-x="45"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ units="px"
+ inkscape:snap-grids="false">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4919"
+ originx="-299.99048"
+ originy="-124.88544" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata3428">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-299.99051,-910.60174)"
+ style="display:inline">
+ <rect
+ style="fill:#000000;fill-opacity:1;stroke:#080808;stroke-width:3.075562;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;image-rendering:auto"
+ id="rect4872"
+ width="7.236938"
+ height="7.236938"
+ x="302.46579"
+ y="918.70203"
+ rx="0.008277542"
+ ry="0.044642854" />
+ <rect
+ ry="0.0078140106"
+ rx="0.00144885"
+ y="921.68713"
+ x="305.4509"
+ height="1.2667092"
+ width="1.2667092"
+ id="rect4419"
+ style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:2.8125;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:14.39999962;stroke-dasharray:none;stroke-opacity:1;image-rendering:auto" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="Layer 2"
+ style="display:inline"
+ transform="translate(-299.99051,-602.33402)">
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:2.00754595;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 308.94445,609.1247 c 0,0 0.0159,-2.38773 0.0784,-3.49702 0.0816,-1.44553 1.96247,-2.34204 3.19909,-2.22236 1.2066,0.11676 2.67091,1.33032 2.77304,2.73882 0.0866,1.19436 0.094,1.94763 0.094,1.94763"
+ id="path1498"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="csasc" />
+ </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-win/decrypt-inactive-18.svg
@@ -0,0 +1,168 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ viewBox="0 0 16.874999 16.875"
+ id="svg3423"
+ version="1.1"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06"
+ sodipodi:docname="decrypt-inactive-18.svg"
+ inkscape:export-filename="/Users/pbr/enigmail/Logo/Enigmail Icon V3 16x16.png"
+ inkscape:export-xdpi="2.6199999"
+ inkscape:export-ydpi="2.6199999">
+ <defs
+ id="defs3425">
+ <linearGradient
+ id="linearGradient4972"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#d5d211;stop-opacity:1;"
+ offset="0"
+ id="stop4974" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4964"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4966" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2780">
+ <stop
+ id="stop2782"
+ style="stop-color:#ffffff"
+ offset="0" />
+ <stop
+ id="stop2784"
+ style="stop-color:#ffffff;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2834">
+ <stop
+ id="stop2836"
+ style="stop-color:#e59a00"
+ offset="0" />
+ <stop
+ id="stop2838"
+ style="stop-color:#faff7d;stop-opacity:.18367"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3057">
+ <stop
+ id="stop3059"
+ style="stop-color:#282828"
+ offset="0" />
+ <stop
+ id="stop3061"
+ style="stop-color:#282828;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2713">
+ <stop
+ id="stop2715"
+ style="stop-color:#000000"
+ offset="0" />
+ <stop
+ id="stop2717"
+ style="stop-color:#ffffff"
+ offset="1" />
+ </linearGradient>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="20"
+ inkscape:cx="5.7254224"
+ inkscape:cy="8.1578366"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer2"
+ showgrid="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1163"
+ inkscape:window-height="728"
+ inkscape:window-x="45"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ units="px"
+ inkscape:snap-grids="false">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4919"
+ originx="-299.99048"
+ originy="-124.88544" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata3428">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-299.99051,-910.60174)"
+ style="display:inline">
+ <rect
+ style="fill:#4d4d4d;fill-opacity:1;stroke:#4d4d4d;stroke-width:3.075562;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;image-rendering:auto"
+ id="rect4872"
+ width="7.236938"
+ height="7.236938"
+ x="302.46579"
+ y="918.70203"
+ rx="0.008277542"
+ ry="0.044642854" />
+ <rect
+ ry="0.0078140106"
+ rx="0.00144885"
+ y="921.68713"
+ x="305.4509"
+ height="1.2667092"
+ width="1.2667092"
+ id="rect4419"
+ style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:2.8125;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:14.39999962;stroke-dasharray:none;stroke-opacity:1;image-rendering:auto" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="Layer 2"
+ style="display:inline"
+ transform="translate(-299.99051,-602.33402)">
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#4d4d4d;stroke-width:2.00754595;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 308.94445,609.1247 c 0,0 0.0159,-2.38773 0.0784,-3.49702 0.0816,-1.44553 1.96247,-2.34204 3.19909,-2.22236 1.2066,0.11676 2.67091,1.33032 2.77304,2.73882 0.0866,1.19436 0.094,1.94763 0.094,1.94763"
+ id="path1498"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="csasc" />
+ </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-win/encrypt-active-18.svg
@@ -0,0 +1,297 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ viewBox="0 0 16.874999 16.875"
+ id="svg3423"
+ version="1.1"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06"
+ sodipodi:docname="encrypt-active.svg"
+ inkscape:export-filename="/Users/pbr/enigmail/Logo/Enigmail Icon V3 16x16.png"
+ inkscape:export-xdpi="2.6199999"
+ inkscape:export-ydpi="2.6199999">
+ <defs
+ id="defs3425">
+ <linearGradient
+ id="linearGradient4972"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#d5d211;stop-opacity:1;"
+ offset="0"
+ id="stop4974" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4964"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4966" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4983"
+ y2="11.711"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="35.995998"
+ gradientTransform="matrix(7.1505593,0,0,8.534094,228.71232,184.93067)"
+ y1="1.6246001"
+ x1="35.995998"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient2780">
+ <stop
+ id="stop2782"
+ style="stop-color:#ffffff"
+ offset="0" />
+ <stop
+ id="stop2784"
+ style="stop-color:#ffffff;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4980"
+ y2="6.0918002"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="39.723999"
+ gradientTransform="matrix(7.1746573,0,0,8.6675651,228.71232,177.84541)"
+ y1="11.17"
+ x1="38.362999"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient2834">
+ <stop
+ id="stop2836"
+ style="stop-color:#e59a00"
+ offset="0" />
+ <stop
+ id="stop2838"
+ style="stop-color:#faff7d;stop-opacity:.18367"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4971"
+ y2="7.5862999"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="38.782001"
+ gradientTransform="matrix(7.1505593,0,0,8.6966416,228.71232,177.84541)"
+ y1="9.5746002"
+ x1="38.782001"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4968"
+ y2="-13.17"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="33.183998"
+ gradientTransform="matrix(9.6839253,0,0,6.8812942,222.29523,170.28389)"
+ y1="24.385"
+ x1="33.183998"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4965"
+ y2="-13.17"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="33.183998"
+ gradientTransform="matrix(9.4687954,0,0,5.4755733,228.71232,182.51659)"
+ y1="-4.6247249"
+ x1="33.003456"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3057">
+ <stop
+ id="stop3059"
+ style="stop-color:#282828"
+ offset="0" />
+ <stop
+ id="stop3061"
+ style="stop-color:#282828;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3151"
+ y2="52.609001"
+ spreadMethod="reflect"
+ gradientUnits="userSpaceOnUse"
+ x2="44.723999"
+ gradientTransform="matrix(9.9301464,0,0,6.770848,212.4891,154.91826)"
+ y1="52.609001"
+ x1="29.614"
+ inkscape:collect="always">
+ <stop
+ id="stop2703"
+ style="stop-color:#db9300"
+ offset="0" />
+ <stop
+ id="stop2770"
+ style="stop-color:#fac700"
+ offset=".75" />
+ <stop
+ id="stop2705"
+ style="stop-color:#fff363"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2713">
+ <stop
+ id="stop2715"
+ style="stop-color:#000000"
+ offset="0" />
+ <stop
+ id="stop2717"
+ style="stop-color:#ffffff"
+ offset="1" />
+ </linearGradient>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="25"
+ inkscape:cx="6.7707505"
+ inkscape:cy="8.9438348"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1239"
+ inkscape:window-height="755"
+ inkscape:window-x="45"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ units="px">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4919"
+ originx="-284.74604"
+ originy="-71.484602" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata3428">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="Layer 2"
+ style="display:inline"
+ transform="translate(-284.74607,-655.73485)"
+ sodipodi:insensitive="true">
+ <g
+ transform="matrix(0.03532479,0,0,0.03960741,274.23987,648.41157)"
+ inkscape:label="Layer 2"
+ id="layer2-4">
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,184.8967 c -87.94487,0 -158.74505,56.60787 -158.74505,126.92368 v 141.02397 h 44.09513 V 311.82038 c 0,-50.7856 51.13613,-91.66768 114.64992,-91.66768 63.51378,0 114.64993,40.88208 114.64993,91.66768 V 452.84435 H 669.6692 V 311.82038 c 0,-70.31581 -70.80018,-126.92368 -158.74505,-126.92368 z"
+ style="fill:#282828;fill-rule:evenodd;stroke-width:3.01772833"
+ id="rect2723" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,194.10762 c -83.05952,0 -149.92515,57.12633 -149.92515,122.29246 v 131.4673 c 0,49.3598 38.45179,91.27181 93.15445,108.75267 C 414.96119,542.36212 387.4587,511.79338 387.4587,475.54598 V 309.48131 c 0,-49.83274 51.13176,-93.25814 114.64554,-93.25814 h 17.6398 c 63.51379,0 114.64994,43.4254 114.64994,93.25814 v 166.06467 c 0,36.2474 -27.5025,66.81614 -66.69476,81.07407 54.69829,-17.48086 93.15007,-59.39287 93.15007,-108.75267 v -131.4673 c 0,-65.16613 -66.86561,-122.29246 -149.92514,-122.29246 z"
+ style="opacity:0.65;fill:url(#linearGradient4983);fill-rule:evenodd;stroke-width:3.01772833"
+ id="path3082" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,187.88807 c -85.48249,0 -154.30223,54.6531 -154.30223,122.5412 v 136.95677 c 0,51.41616 39.57782,95.07978 95.87093,113.29631 -40.33581,-14.85349 -68.6401,-46.70438 -68.6401,-84.46164 v -173.001 c 0,-51.91359 52.62584,-101.87944 117.99736,-101.87944 l 18.15245,-3.4e-4 c 65.36715,0 117.99298,49.96583 117.99298,101.87944 v 173.00099 c 0,37.75727 -28.3043,69.60817 -68.64012,84.46163 56.29315,-18.21653 95.87096,-61.88014 95.87096,-113.2963 V 310.4289 c 0,-67.8881 -68.81976,-122.54118 -154.30223,-122.54118 z"
+ style="opacity:0.5;fill:url(#linearGradient4980);fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2931" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,199.88821 c -83.05952,0 -149.92515,45.52381 -149.92515,111.93356 v 133.97558 c 0,50.29514 38.45179,92.99187 93.15445,110.82307 C 414.96119,542.08221 387.4587,510.93897 387.4587,474.00145 V 304.76987 c 0,-50.78207 51.13176,-91.72024 114.64554,-91.72024 h 17.6398 c 63.51379,0 114.64994,40.93817 114.64994,91.72024 v 169.23158 c 0,36.93752 -27.5025,68.08076 -66.69476,82.61897 54.69829,-17.8312 93.15007,-60.52793 93.15007,-110.82307 V 311.82177 c 0,-66.40975 -66.86561,-111.93356 -149.92514,-111.93356 z"
+ style="opacity:0.5;fill:url(#linearGradient4971);fill-rule:evenodd;stroke-width:3.01772833"
+ id="rect2875" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,192.03933 c -117.25692,0 -144.31249,81.67662 -144.31249,118.61765 0,110.81954 9.01707,110.81954 9.01707,0 0,-81.27025 72.13215,-113.34537 135.29542,-113.34537 63.16326,7e-4 135.2954,35.39264 135.2954,113.34537 0,110.81954 9.02146,110.81954 9.02146,0 0,-36.94103 -27.05996,-118.61765 -144.31686,-118.61765 z"
+ style="opacity:0.8;fill:url(#linearGradient4968);fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2933" />
+ <rect
+ x="323.9566"
+ y="357.59714"
+ width="371.55212"
+ height="253.34859"
+ ry="1.9118049"
+ rx="2.3074045"
+ style="fill:url(#linearGradient3151);fill-rule:evenodd;stroke-width:3.01772833"
+ id="rect1941" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 331.08188,610.95331 c 8.3528,-8.10605 83.73569,-14.49145 178.38542,-14.49145 93.35299,0 167.50834,6.30892 177.61479,14.26203 -0.18461,0 -0.13845,0.22942 -0.27689,0.22942 z"
+ style="opacity:0.5;fill:#000000;fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2768" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,197.15513 c -114.64992,0 -141.10525,67.66709 -141.10525,97.06436 0,88.18571 8.81991,88.18571 8.81991,0 0,-64.66912 70.52414,-92.86779 132.28534,-92.86779 61.76122,6.1e-4 132.28972,30.83556 132.28972,92.86779 0,88.18571 8.81553,88.18571 8.81553,0 0,-29.39727 -26.45532,-97.06436 -141.10525,-97.06436 z"
+ style="opacity:1;fill:url(#linearGradient4965);fill-opacity:1;fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2885" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 345.56746,357.59712 c 7.93046,8.11368 79.50178,14.47618 169.36574,14.47618 88.63275,0 159.02983,-6.29748 168.62525,-14.2506 -0.13145,-0.0122 -0.13145,-0.22558 -0.26289,-0.22558 z"
+ style="opacity:0.5;fill:#ffffff;fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2727" />
+ </g>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer1"
+ inkscape:label="Layer 3"
+ sodipodi:insensitive="true">
+ <g
+ id="g5151"
+ transform="translate(-1.0275364)">
+ <path
+ inkscape:connector-curvature="0"
+ id="path5137"
+ d="M 9.8625001,9.3749997 7.1249998,9.4124997 7.1624998,14.1 9.8250001,14.0625"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:0.93749994;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5139"
+ d="M 9.9750001,11.6438 7.4249998,11.6813"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#0000ff;fill-opacity:1;fill-rule:evenodd;stroke:#0000ff;stroke-width:0.93749994;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ </g>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer3"
+ inkscape:label="Layer 4"
+ sodipodi:insensitive="true">
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#008000;stroke-width:1.74388254;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 11.90849,12.21419 c 0,0 1.39836,1.035106 2.568841,2.4924 0.454732,-3.871985 1.725652,-6.1303502 1.725652,-6.1303502"
+ id="path4586"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccc" />
+ </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-win/encrypt-disabled-18.svg
@@ -0,0 +1,322 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ viewBox="0 0 16.874999 16.875"
+ id="svg3423"
+ version="1.1"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06"
+ sodipodi:docname="encrypt-disabled.svg"
+ inkscape:export-filename="/Users/pbr/enigmail/Logo/Enigmail Icon V3 16x16.png"
+ inkscape:export-xdpi="2.6199999"
+ inkscape:export-ydpi="2.6199999">
+ <defs
+ id="defs3425">
+ <linearGradient
+ id="linearGradient4972"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#d5d211;stop-opacity:1;"
+ offset="0"
+ id="stop4974" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4964"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4966" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4983"
+ y2="11.711"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="35.995998"
+ gradientTransform="matrix(7.1505593,0,0,8.534094,228.71232,184.93067)"
+ y1="1.6246001"
+ x1="35.995998"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient2780">
+ <stop
+ id="stop2782"
+ style="stop-color:#ffffff"
+ offset="0" />
+ <stop
+ id="stop2784"
+ style="stop-color:#ffffff;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4980"
+ y2="6.0918002"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="39.723999"
+ gradientTransform="matrix(7.1746573,0,0,8.6675651,228.71232,177.84541)"
+ y1="11.17"
+ x1="38.362999"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient2834">
+ <stop
+ id="stop2836"
+ style="stop-color:#e59a00"
+ offset="0" />
+ <stop
+ id="stop2838"
+ style="stop-color:#faff7d;stop-opacity:.18367"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4971"
+ y2="7.5862999"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="38.782001"
+ gradientTransform="matrix(7.1505593,0,0,8.6966416,228.71232,177.84541)"
+ y1="9.5746002"
+ x1="38.782001"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4968"
+ y2="-13.17"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="33.183998"
+ gradientTransform="matrix(9.6839253,0,0,6.8812942,222.29523,170.28389)"
+ y1="24.385"
+ x1="33.183998"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4965"
+ y2="-13.17"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="33.183998"
+ gradientTransform="matrix(9.4687954,0,0,5.4755733,228.71232,182.51659)"
+ y1="-4.6247249"
+ x1="33.003456"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3057">
+ <stop
+ id="stop3059"
+ style="stop-color:#282828"
+ offset="0" />
+ <stop
+ id="stop3061"
+ style="stop-color:#282828;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3151"
+ y2="52.609001"
+ spreadMethod="reflect"
+ gradientUnits="userSpaceOnUse"
+ x2="44.723999"
+ gradientTransform="matrix(9.9301464,0,0,6.770848,212.4891,154.91826)"
+ y1="52.609001"
+ x1="29.614"
+ inkscape:collect="always">
+ <stop
+ id="stop2703"
+ style="stop-color:#db9300"
+ offset="0" />
+ <stop
+ id="stop2770"
+ style="stop-color:#fac700"
+ offset=".75" />
+ <stop
+ id="stop2705"
+ style="stop-color:#fff363"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2713">
+ <stop
+ id="stop2715"
+ style="stop-color:#000000"
+ offset="0" />
+ <stop
+ id="stop2717"
+ style="stop-color:#ffffff"
+ offset="1" />
+ </linearGradient>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Greyscale"
+ id="filter5183">
+ <feColorMatrix
+ values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"
+ id="feColorMatrix5181"
+ result="fbSourceGraphic" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix5185" />
+ <feColorMatrix
+ id="feColorMatrix5187"
+ values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"
+ in="fbSourceGraphic"
+ result="fbSourceGraphic" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix5299" />
+ <feColorMatrix
+ id="feColorMatrix5301"
+ values="0.21 0.72 0.072 -0.3 0 0.21 0.72 0.072 -0.3 0 0.21 0.72 0.072 -0.3 0 0 0 0 1 0 "
+ in="fbSourceGraphic"
+ result="fbSourceGraphic" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix5380" />
+ <feColorMatrix
+ id="feColorMatrix5382"
+ values="0.21 0.72 0.072 0.4 0 0.21 0.72 0.072 0.4 0 0.21 0.72 0.072 0.4 0 0 0 0 1 0 "
+ in="fbSourceGraphic" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="25"
+ inkscape:cx="5.0307505"
+ inkscape:cy="7.0342183"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer2"
+ showgrid="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1239"
+ inkscape:window-height="755"
+ inkscape:window-x="45"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ units="px">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4919"
+ originx="-284.74604"
+ originy="-71.484602" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata3428">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="Layer 2"
+ style="display:inline"
+ transform="translate(-284.74607,-655.73485)">
+ <g
+ transform="matrix(0.03532479,0,0,0.03960741,274.23987,648.41157)"
+ inkscape:label="Layer 2"
+ id="layer2-4"
+ style="filter:url(#filter5183)">
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,184.8967 c -87.94487,0 -158.74505,56.60787 -158.74505,126.92368 v 141.02397 h 44.09513 V 311.82038 c 0,-50.7856 51.13613,-91.66768 114.64992,-91.66768 63.51378,0 114.64993,40.88208 114.64993,91.66768 V 452.84435 H 669.6692 V 311.82038 c 0,-70.31581 -70.80018,-126.92368 -158.74505,-126.92368 z"
+ style="fill:#282828;fill-rule:evenodd;stroke-width:3.01772833"
+ id="rect2723" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,194.10762 c -83.05952,0 -149.92515,57.12633 -149.92515,122.29246 v 131.4673 c 0,49.3598 38.45179,91.27181 93.15445,108.75267 C 414.96119,542.36212 387.4587,511.79338 387.4587,475.54598 V 309.48131 c 0,-49.83274 51.13176,-93.25814 114.64554,-93.25814 h 17.6398 c 63.51379,0 114.64994,43.4254 114.64994,93.25814 v 166.06467 c 0,36.2474 -27.5025,66.81614 -66.69476,81.07407 54.69829,-17.48086 93.15007,-59.39287 93.15007,-108.75267 v -131.4673 c 0,-65.16613 -66.86561,-122.29246 -149.92514,-122.29246 z"
+ style="opacity:0.65;fill:url(#linearGradient4983);fill-rule:evenodd;stroke-width:3.01772833"
+ id="path3082" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,187.88807 c -85.48249,0 -154.30223,54.6531 -154.30223,122.5412 v 136.95677 c 0,51.41616 39.57782,95.07978 95.87093,113.29631 -40.33581,-14.85349 -68.6401,-46.70438 -68.6401,-84.46164 v -173.001 c 0,-51.91359 52.62584,-101.87944 117.99736,-101.87944 l 18.15245,-3.4e-4 c 65.36715,0 117.99298,49.96583 117.99298,101.87944 v 173.00099 c 0,37.75727 -28.3043,69.60817 -68.64012,84.46163 56.29315,-18.21653 95.87096,-61.88014 95.87096,-113.2963 V 310.4289 c 0,-67.8881 -68.81976,-122.54118 -154.30223,-122.54118 z"
+ style="opacity:0.5;fill:url(#linearGradient4980);fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2931" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,199.88821 c -83.05952,0 -149.92515,45.52381 -149.92515,111.93356 v 133.97558 c 0,50.29514 38.45179,92.99187 93.15445,110.82307 C 414.96119,542.08221 387.4587,510.93897 387.4587,474.00145 V 304.76987 c 0,-50.78207 51.13176,-91.72024 114.64554,-91.72024 h 17.6398 c 63.51379,0 114.64994,40.93817 114.64994,91.72024 v 169.23158 c 0,36.93752 -27.5025,68.08076 -66.69476,82.61897 54.69829,-17.8312 93.15007,-60.52793 93.15007,-110.82307 V 311.82177 c 0,-66.40975 -66.86561,-111.93356 -149.92514,-111.93356 z"
+ style="opacity:0.5;fill:url(#linearGradient4971);fill-rule:evenodd;stroke-width:3.01772833"
+ id="rect2875" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,192.03933 c -117.25692,0 -144.31249,81.67662 -144.31249,118.61765 0,110.81954 9.01707,110.81954 9.01707,0 0,-81.27025 72.13215,-113.34537 135.29542,-113.34537 63.16326,7e-4 135.2954,35.39264 135.2954,113.34537 0,110.81954 9.02146,110.81954 9.02146,0 0,-36.94103 -27.05996,-118.61765 -144.31686,-118.61765 z"
+ style="opacity:0.8;fill:url(#linearGradient4968);fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2933" />
+ <rect
+ x="323.9566"
+ y="357.59714"
+ width="371.55212"
+ height="253.34859"
+ ry="1.9118049"
+ rx="2.3074045"
+ style="fill:url(#linearGradient3151);fill-rule:evenodd;stroke-width:3.01772833"
+ id="rect1941" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 331.08188,610.95331 c 8.3528,-8.10605 83.73569,-14.49145 178.38542,-14.49145 93.35299,0 167.50834,6.30892 177.61479,14.26203 -0.18461,0 -0.13845,0.22942 -0.27689,0.22942 z"
+ style="opacity:0.5;fill:#000000;fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2768" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,197.15513 c -114.64992,0 -141.10525,67.66709 -141.10525,97.06436 0,88.18571 8.81991,88.18571 8.81991,0 0,-64.66912 70.52414,-92.86779 132.28534,-92.86779 61.76122,6.1e-4 132.28972,30.83556 132.28972,92.86779 0,88.18571 8.81553,88.18571 8.81553,0 0,-29.39727 -26.45532,-97.06436 -141.10525,-97.06436 z"
+ style="opacity:1;fill:url(#linearGradient4965);fill-opacity:1;fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2885" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 345.56746,357.59712 c 7.93046,8.11368 79.50178,14.47618 169.36574,14.47618 88.63275,0 159.02983,-6.29748 168.62525,-14.2506 -0.13145,-0.0122 -0.13145,-0.22558 -0.26289,-0.22558 z"
+ style="opacity:0.5;fill:#ffffff;fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2727" />
+ </g>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer1"
+ inkscape:label="Layer 3"
+ sodipodi:insensitive="true">
+ <g
+ id="g5307">
+ <path
+ inkscape:connector-curvature="0"
+ id="path5137"
+ d="M 8.8349637,9.3749997 6.0974634,9.4124997 6.1349634,14.1 8.7974637,14.0625"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#00000c;stroke-width:0.93749994;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5139"
+ d="M 8.9474637,11.6438 6.3974634,11.6813"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#0000ff;fill-opacity:1;fill-rule:evenodd;stroke:#00000c;stroke-width:0.93749994;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ </g>
+ </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-win/encrypt-inactive-18.svg
@@ -0,0 +1,330 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ viewBox="0 0 16.874999 16.875"
+ id="svg3423"
+ version="1.1"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06"
+ sodipodi:docname="encrypt-inactive.svg"
+ inkscape:export-filename="/Users/pbr/enigmail/Logo/Enigmail Icon V3 16x16.png"
+ inkscape:export-xdpi="2.6199999"
+ inkscape:export-ydpi="2.6199999">
+ <defs
+ id="defs3425">
+ <linearGradient
+ id="linearGradient4972"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#d5d211;stop-opacity:1;"
+ offset="0"
+ id="stop4974" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4964"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4966" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4983"
+ y2="11.711"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="35.995998"
+ gradientTransform="matrix(7.1505593,0,0,8.534094,228.71232,184.93067)"
+ y1="1.6246001"
+ x1="35.995998"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient2780">
+ <stop
+ id="stop2782"
+ style="stop-color:#ffffff"
+ offset="0" />
+ <stop
+ id="stop2784"
+ style="stop-color:#ffffff;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4980"
+ y2="6.0918002"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="39.723999"
+ gradientTransform="matrix(7.1746573,0,0,8.6675651,228.71232,177.84541)"
+ y1="11.17"
+ x1="38.362999"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient2834">
+ <stop
+ id="stop2836"
+ style="stop-color:#e59a00"
+ offset="0" />
+ <stop
+ id="stop2838"
+ style="stop-color:#faff7d;stop-opacity:.18367"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4971"
+ y2="7.5862999"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="38.782001"
+ gradientTransform="matrix(7.1505593,0,0,8.6966416,228.71232,177.84541)"
+ y1="9.5746002"
+ x1="38.782001"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4968"
+ y2="-13.17"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="33.183998"
+ gradientTransform="matrix(9.6839253,0,0,6.8812942,222.29523,170.28389)"
+ y1="24.385"
+ x1="33.183998"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4965"
+ y2="-13.17"
+ xlink:href="#linearGradient2780"
+ gradientUnits="userSpaceOnUse"
+ x2="33.183998"
+ gradientTransform="matrix(9.4687954,0,0,5.4755733,228.71232,182.51659)"
+ y1="-4.6247249"
+ x1="33.003456"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient3057">
+ <stop
+ id="stop3059"
+ style="stop-color:#282828"
+ offset="0" />
+ <stop
+ id="stop3061"
+ style="stop-color:#282828;stop-opacity:0"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3151"
+ y2="52.609001"
+ spreadMethod="reflect"
+ gradientUnits="userSpaceOnUse"
+ x2="44.723999"
+ gradientTransform="matrix(9.9301464,0,0,6.770848,212.4891,154.91826)"
+ y1="52.609001"
+ x1="29.614"
+ inkscape:collect="always">
+ <stop
+ id="stop2703"
+ style="stop-color:#db9300"
+ offset="0" />
+ <stop
+ id="stop2770"
+ style="stop-color:#fac700"
+ offset=".75" />
+ <stop
+ id="stop2705"
+ style="stop-color:#fff363"
+ offset="1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2713">
+ <stop
+ id="stop2715"
+ style="stop-color:#000000"
+ offset="0" />
+ <stop
+ id="stop2717"
+ style="stop-color:#ffffff"
+ offset="1" />
+ </linearGradient>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Greyscale"
+ id="filter5183">
+ <feColorMatrix
+ values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"
+ id="feColorMatrix5181"
+ result="fbSourceGraphic" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix5185" />
+ <feColorMatrix
+ id="feColorMatrix5187"
+ values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"
+ in="fbSourceGraphic"
+ result="fbSourceGraphic" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix5299" />
+ <feColorMatrix
+ id="feColorMatrix5301"
+ values="0.21 0.72 0.072 -0.3 0 0.21 0.72 0.072 -0.3 0 0.21 0.72 0.072 -0.3 0 0 0 0 1 0 "
+ in="fbSourceGraphic" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="25"
+ inkscape:cx="5.0307505"
+ inkscape:cy="6.0232448"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer3"
+ showgrid="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1239"
+ inkscape:window-height="755"
+ inkscape:window-x="45"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ units="px">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4919"
+ originx="-284.74604"
+ originy="-71.484602" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata3428">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="Layer 2"
+ style="display:inline"
+ transform="translate(-284.74607,-655.73485)"
+ sodipodi:insensitive="true">
+ <g
+ transform="matrix(0.03532479,0,0,0.03960741,274.23987,648.41157)"
+ inkscape:label="Layer 2"
+ id="layer2-4"
+ style="filter:url(#filter5183)">
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,184.8967 c -87.94487,0 -158.74505,56.60787 -158.74505,126.92368 v 141.02397 h 44.09513 V 311.82038 c 0,-50.7856 51.13613,-91.66768 114.64992,-91.66768 63.51378,0 114.64993,40.88208 114.64993,91.66768 V 452.84435 H 669.6692 V 311.82038 c 0,-70.31581 -70.80018,-126.92368 -158.74505,-126.92368 z"
+ style="fill:#282828;fill-rule:evenodd;stroke-width:3.01772833"
+ id="rect2723" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,194.10762 c -83.05952,0 -149.92515,57.12633 -149.92515,122.29246 v 131.4673 c 0,49.3598 38.45179,91.27181 93.15445,108.75267 C 414.96119,542.36212 387.4587,511.79338 387.4587,475.54598 V 309.48131 c 0,-49.83274 51.13176,-93.25814 114.64554,-93.25814 h 17.6398 c 63.51379,0 114.64994,43.4254 114.64994,93.25814 v 166.06467 c 0,36.2474 -27.5025,66.81614 -66.69476,81.07407 54.69829,-17.48086 93.15007,-59.39287 93.15007,-108.75267 v -131.4673 c 0,-65.16613 -66.86561,-122.29246 -149.92514,-122.29246 z"
+ style="opacity:0.65;fill:url(#linearGradient4983);fill-rule:evenodd;stroke-width:3.01772833"
+ id="path3082" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,187.88807 c -85.48249,0 -154.30223,54.6531 -154.30223,122.5412 v 136.95677 c 0,51.41616 39.57782,95.07978 95.87093,113.29631 -40.33581,-14.85349 -68.6401,-46.70438 -68.6401,-84.46164 v -173.001 c 0,-51.91359 52.62584,-101.87944 117.99736,-101.87944 l 18.15245,-3.4e-4 c 65.36715,0 117.99298,49.96583 117.99298,101.87944 v 173.00099 c 0,37.75727 -28.3043,69.60817 -68.64012,84.46163 56.29315,-18.21653 95.87096,-61.88014 95.87096,-113.2963 V 310.4289 c 0,-67.8881 -68.81976,-122.54118 -154.30223,-122.54118 z"
+ style="opacity:0.5;fill:url(#linearGradient4980);fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2931" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,199.88821 c -83.05952,0 -149.92515,45.52381 -149.92515,111.93356 v 133.97558 c 0,50.29514 38.45179,92.99187 93.15445,110.82307 C 414.96119,542.08221 387.4587,510.93897 387.4587,474.00145 V 304.76987 c 0,-50.78207 51.13176,-91.72024 114.64554,-91.72024 h 17.6398 c 63.51379,0 114.64994,40.93817 114.64994,91.72024 v 169.23158 c 0,36.93752 -27.5025,68.08076 -66.69476,82.61897 54.69829,-17.8312 93.15007,-60.52793 93.15007,-110.82307 V 311.82177 c 0,-66.40975 -66.86561,-111.93356 -149.92514,-111.93356 z"
+ style="opacity:0.5;fill:url(#linearGradient4971);fill-rule:evenodd;stroke-width:3.01772833"
+ id="rect2875" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,192.03933 c -117.25692,0 -144.31249,81.67662 -144.31249,118.61765 0,110.81954 9.01707,110.81954 9.01707,0 0,-81.27025 72.13215,-113.34537 135.29542,-113.34537 63.16326,7e-4 135.2954,35.39264 135.2954,113.34537 0,110.81954 9.02146,110.81954 9.02146,0 0,-36.94103 -27.05996,-118.61765 -144.31686,-118.61765 z"
+ style="opacity:0.8;fill:url(#linearGradient4968);fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2933" />
+ <rect
+ x="323.9566"
+ y="357.59714"
+ width="371.55212"
+ height="253.34859"
+ ry="1.9118049"
+ rx="2.3074045"
+ style="fill:url(#linearGradient3151);fill-rule:evenodd;stroke-width:3.01772833"
+ id="rect1941" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 331.08188,610.95331 c 8.3528,-8.10605 83.73569,-14.49145 178.38542,-14.49145 93.35299,0 167.50834,6.30892 177.61479,14.26203 -0.18461,0 -0.13845,0.22942 -0.27689,0.22942 z"
+ style="opacity:0.5;fill:#000000;fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2768" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 510.92415,197.15513 c -114.64992,0 -141.10525,67.66709 -141.10525,97.06436 0,88.18571 8.81991,88.18571 8.81991,0 0,-64.66912 70.52414,-92.86779 132.28534,-92.86779 61.76122,6.1e-4 132.28972,30.83556 132.28972,92.86779 0,88.18571 8.81553,88.18571 8.81553,0 0,-29.39727 -26.45532,-97.06436 -141.10525,-97.06436 z"
+ style="opacity:1;fill:url(#linearGradient4965);fill-opacity:1;fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2885" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 345.56746,357.59712 c 7.93046,8.11368 79.50178,14.47618 169.36574,14.47618 88.63275,0 159.02983,-6.29748 168.62525,-14.2506 -0.13145,-0.0122 -0.13145,-0.22558 -0.26289,-0.22558 z"
+ style="opacity:0.5;fill:#ffffff;fill-rule:evenodd;stroke-width:3.01772833"
+ id="path2727" />
+ </g>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer1"
+ inkscape:label="Layer 3"
+ sodipodi:insensitive="true">
+ <g
+ id="g5307">
+ <path
+ inkscape:connector-curvature="0"
+ id="path5137"
+ d="M 8.8349637,9.3749997 6.0974634,9.4124997 6.1349634,14.1 8.7974637,14.0625"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#00000c;stroke-width:0.93749994;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5139"
+ d="M 8.9474637,11.6438 6.3974634,11.6813"
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#0000ff;fill-opacity:1;fill-rule:evenodd;stroke:#00000c;stroke-width:0.93749994;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
+ </g>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer3"
+ inkscape:label="Layer 4">
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:2.24979424;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 15.667684,15.742702 c -3.40903,-2.920537 -4.694046,-5.522759 -4.694046,-5.522759"
+ id="path4586"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:2.15561461;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 15.747607,10.026563 -5.09709,5.759107"
+ id="path5174"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ </g>
+</svg>
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ae175f67fd5e757913691a4773272cc7e0f03d10
GIT binary patch
literal 1017
zc$@+G0|xwwP)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D1CB{VK~y+Th16S2
z6jvAr@DIfcTdip=n#LHmF@=bs8XIq`4KZF5q(KD}^np;Hj2fdzQn41a*-&hV7bHkZ
zu~s2?DNptCU}EY63TnA1i-7D5aIA~E?k>yh?7%Kx|FfK8Hnj0e{+V;moc-;YZ!=5p
ztB%4XtK(SW$@YTJi=PxM*d&*UiDHo{NQ|6M8YP;m6Kx(L+8WvPe`TVb_7wHy?cKuD
zcvy(q40aSH7fK`^NkHPvIWi05jX+Ni6Xi)_Vs#WRwssb$sNP54vG-}d@U>=(fb)BH
z{qYSyJG$@(AS;nfWIj*}u8YG&cA&ol-y)gl>L^K?89|%vJoNksdmnBP-j=V0yI~t^
zY6%b2<*<AC$2R~~0$B%iL5OG%atH9Q2GJLiimqoTmt^(TZ0F$1YW6+e%-+@v;cMF|
zl&UrCx`p*SMeN_Oko6g{{eX@D&c^jRzN7{Cx0uh7RCINhF3IYz-NA;dmVIqm>}|^y
zKF3}$=s3jgySc1a=5lap5^I*&QJn4;&>Ex*@Vy{WI$ASbQqlFiY-vjO*=4N1sOLa$
zDSEt540at80p&;5o9t}lZDr%rPuM>>h5@|*uHgC#a5ct@%pX%y@2aC>g|)lvOXW>N
z9_ylvM?Cjf?>{g6$|<2Ml^nbYcuOj4AH=anH0mcB9wG_=bfOIh(S~?Qy(=Yxu2kF)
z3V@yAKwl~Ay}zK>jlvkXDU6;gJaVp(1Bq7lPn~3<2JkT8AWr-zPP}nK8zSh>s}%R6
z6C41lDJNKSR<K&TiF;~x2;)_=2tRFLBX>J%A5IM!lVcS0)d|?ZOYs))c_cBa4ax-*
zU9EWjDnU<A@laPj2VIxBw=RvH)wV)s)v5!Ia$C<pfsK7hvvi<cKri6Yuc8Gmj60FN
zUZRgAX`-pt?u)AxZ|iA1_D}3@-_60lf8&wCCRQ%4!2!nV?fN+h+B{1MfcwZLJa(pu
z;o68VxPjiTMW)H^MCv7=&E<G>JNsJFfmU-!YmY>|{M9&&4<!RB2X+n6F3b}GL^iBd
z;zcRK{IjSwsCB6v>U+$d2g}jh3J&Qin`{3@ZX*C`0IGl<GJ9#B4*i{nc@D;D$V|C)
zta`7l<*-NP@bK&Lm}~O(c;Te`fgcB8!%YhkgK->^jF_j5m0QQEuYMi>SLE&S0-6rM
n{9l-DqUM@;MJ&pMH;w2YsxCo>5wLfA00000NkvXXu0mjf3)k7T
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..265eda8ebf08c7d2ade38b731cbae96db604d38f
GIT binary patch
literal 715
zc$@*t0yO=JP)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0$@o*K~y+TrI77Q
zQ(+i~_o;9G4}&OaWJzJ=TOv^kQHU|YJ|;m>Ayj@yGDBLj#ju(&DN}qKk|05E(uaZw
zn`PyeJ9BRBIh&7d)($&Ix1G&tzn(KsK_E`o1vfn0&;7gLzBSo!x8`fPmYpV7^N#kf
z&D*Qg_Wz+sJ%$24G}b_)6IUp;)quO9YgNB<!`;4D>uE3G#mQ?Br<<tcsHX}4b%dXt
z0e5qEZgRIVKQANwVnEDy72?OERGd0Vh4EU%115yW&SB=(o~_GgOojU3&?zKCJ&-<C
zLv++o$$6TR?lOd5>5-i8X7Y=08w(rN$mZ~x3-xohI;6rjNKQROXFZj;^K_BB1mXQz
zB!V4Sj=Y4ovqDfKo5N?>SKz%@1bKcGiGZ2i_yS#=ZJ`O?2zl;4+hhP~=^ePiW2<T;
zJ8tVit=F=jM8+>erWT~5Z<(x%E|Cr@&p$%)#|ToAo!$B=Je@^v)Y!(5jPwu@?9EJe
zm#u7u9AL801WhfxpsC1nq+?FD$!qv~D}&0srs-Fn8egA`^kFd(Cfkr;(l}{D+;0VM
ztHQUTI+_w3bai<F@`4?tzj{rY4brPfOG)&zk=?WdvDqdh!-EJ6ABVrEq}AVD+~|5x
z<_nJ0!qZ#MEAvWjN~gnmrAjNb-o8>II_|KwZ)3^TfaLdnq!&Mc^p&j*G2Bl|@<`v|
zUDJ<s+tk*^Vt#;0r@Pqd21rvUn6w8gaaX4J!ScW2EyE92U`6H;wI5)&wPxN*?@y^?
xTPHA5iPhzw|CG{GzjG%zdIam0`kf;h&2Qq9)QKj11kwNi002ovPDHLkV1f_TPLBWp
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..b96f683eef8cb216a146981bf361993e9ce17008
GIT binary patch
literal 708
zc$@*m0z3VQP)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0$E8!K~y+TrI77Q
zlTjFl_o;9D9|lp9WR1egw}wP0L?Olmza~LYAyj@yGDA|CA**I6WsdnWBte4Sqz?%Z
zHp|Mby*M{_p3P&MwXtWm?b)36>pbfe1g36XaKqs~_wR!HR&0f<wLs-;+1=%6-qrcJ
zd1n=G{~xl{-CiUFCmLvU`U<7y8aQthw({@%aCPoixjTylapoFCdlMC{^)%_bj?mLH
zaNgXTpWLM@P-dl{+7b6&h4}tB6=zS;$W$#7{w{<j&S9=&U+(g`F10o=avG`N0Ae4i
zAzEvwXsf5BzZ{{LTBMfxnfxMLI&~wDY!6SjTDxejLpo$e%%+7b747Hgit`d8Z_gqb
z&|@|H0-hdCgh#fA*KnZ7bGI1M(m0a-Zg%4fRGe?2NkIo`@gCb`2$|R$Ob%<-d1Px`
zy@ypE!$A_Bx(taLkcqx#vOX#<=&7{y5UH<YNXIPf)=%K>DW2r9%^_Lo5fZU9Gua)s
zayKMDlLe<~I{2KX!_Sb3+t?<r;4@YR<atHY4<(OpOji1!ghXcAkYv&XX+y%-3&C84
zg^@a%j#%mX>N2Ed3&>#gFX>hgqbw~W(T_TIQ$6DIO-O}?;U7H#-#}@rufL?xalhOf
z7^{WbSRu&sJo;~lQKRyh%7|#a&DOq!?~Vqfz6>I>@(#pQ4nC%EJuX!e)6qTlM}<3h
zYjcDlCbjpm)!Px9J;|g4SW7svB@9>m5$B9Bs==BhAZj_pZrhuE%e~*l$8yI%R*Ci1
qZ-15C<KOuc7(a%Miht*cLh%!e#=5wz>H=N>0000<MNUMnLSTaCLPo9t
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c8e868c2c5cfca3833de5fad81b4073154fe48c0
GIT binary patch
literal 729
zc$@**0w(>5P)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0&Yn}K~y+TrI6oC
zQ(+j#ch#MLKzD;ENwTD{QfDGj3Q>qL!7h^^s1T}MB$*<a%#c+xCS?wP4M~uoU(!WE
zgw3*Y%WclBy=U_^XKn19x$SID`}Us0F-n%&`oI?s&+|SX&hw}?!qt?kb~f&4cQkBo
z``oatLMi_rlGNRr&&`k4(a88^N-b4zHs>`d?d)*1?NPhi@;QFuD)=cK6>POM;k$<5
zlhbfs-<6%*rO(ZgrJq_6^Iie}{s`qKPtfpKHRAqu1V_(e`o`{mmru7BXaj>Mk(}>=
z@SzgCt%?fvQ<QXT5PYFUa;}@nFTkZQs8>cdho_@JJ8P*yDrkXV*Mhg#Qo(tSMx7TC
zetQOqfB{RP=kRovg_V)b;Wh5f_uMIjI5&cXzk}WQJdMsY(gdf6ID40E(vP(82F`(_
zE6T{mxJ(Ds9^*a|8oLCM8j+5?X0k52NDNe*dw}G(VWb2ryY*wZI}2YaW9vg?=|K{<
zHZa+3wsHqVKa<Um)6~K<nhHHdI%a2^yoAqO9$@b^D&3E*DqRwCG9gRv7m@Hp3ldBk
zCoPEkOyDdP_%c{SQ(+rjSz3U&U<K)`l%#59RP|ReF-y`C68WfSH#HzOqeC({0RPZ2
z_<D+)eBDL$j(Zw!V7MA?b15gimj_pcYa>C-Wol1v3E^$G*xEO-=%_>TYai0lcObo*
zUw`3xRGdS45AB?Kn72hKtuN;KnRKd)t=<Y@@;H<BU^(uP!wr;XzS~-U7p}q$m0?-r
z5V7uOw>8P%Qf)QLv-Qa&_79h1W$F7Lk!ngidjcbekts?$OH`_#On2*j;&HYw00000
LNkvXXu0mjfCCyFH
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..322698b33fb416c5d3a8239f82d4f0f00a720bae
GIT binary patch
literal 675
zc$@*F0$lxxP)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0ys%TK~y+TrIO2P
zT2T~+eS!8FoOaMxD5Q=%DfAUeXPI^64bd2pAl|JNQEH2b#)MFBp!I_HE8ex>$smC^
zaNs~Y>)*3B=g85fG);e4?0xp-JO5s5$^IsdMk7}$l|Hpv{Yj-#y%LuHPXsj>4A&PI
z7szBX@caGHYPA|+`*AWFjq;O|lk03Yi$bA*L?VGorGiK#0-a9xIJn7VdO}bFmP#c=
zqfwA+8k@}~VzC(Xdi}q{dAD!q*~8(GK`t*ZnPZ#HhHyBH!C-)RJPtlh$HJ9|iNJwC
z0MqFdS65d^rBa}j<Z?Oo-EOzRaUAS+`%1X-FrA&9JvW=pn9XKrwOYvM^C*|gXfzt=
z^?DeO$H;ajIJSNMd1Q!vdwF`UIWk6H2-okI#bS~3P%fEFqTB7_=H>?dexJFd6wK#y
zIFb##{g8yhe7+0u2LfhaB9vOI)9Em`1iihz#bU8wQRsiiyN~G|kR*lw=A58btCgcX
z60};aveE1HA{Y!}xm;qi*}(R-wgVl4DfV0wl&?5fEEd_5sm&&n34A^ub%KVE_k5wY
zRM6MPFF&N9Lom5!P`BGH=Pz;8{Pgs6I*rk2guA;tI2;bJL%jIcJasyq&lJX}P6|?-
zO6Gj*922x&ucKD0vEHQKU9DCuTrSrREQ)eSu0)X3@m`KS9uL;*HA0~fYbBb~ni%ZR
zfmnM<;39M{zf_#<cDom}CT#yv<V^el8xOmI3Uprx+mDn?_6v`aJ78M+rAhz*002ov
JPDHLkV1lYwIB5U?
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..6485acab9b32cbc5d333cc9b13c2ec277f734763
GIT binary patch
literal 757
zc$@+C0t)?!P)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0*XmQK~y+TrIP(i
zQ&Ako{RjLX22oOENzschDNDp43Na@5YZ3&NLS>L-lhK-+h}9~krm@r^twupFl2OnP
zrlsX{+f}E#ck|d*Id(O-yZO@7xtF`OEhDoB9?s#se9rUzax(rVMxCW_)o(F4>o)7g
z>Nb_i%l{`4I-$+x{Jm8qG<cp6OBq}Z+tl**#>D7$DkgN<96xjkyrY)z_DT}*Tt?vO
zak#E*-x!?HXJtyzPg*QaT?GH3gzzIrNwB{hVXpy!-jf)=y5ryB<AxlSulpFH{tnC!
z9|CVLBm8J3VNC@Hyi_4NYohQoVDvdP@|E?;-JGMEu~Z-yuwZ^v1rf?SPLT!ISp?sm
zK*ZOArMVYy-z*BsSJtN~<F0J?jXe-%ZHRcAX^&46ezKlKI6Z`!TeOl+3^T9MaQng!
zrS{RAgN-(|QunY>zP`4Y)_n@MaW^~He+~jMVmb7d!rBNw-9m)fc0|8g(f+Cr)g~K?
zn|?X|fQ(5MD?#_O!GSsoyG~1PhTx?z{~(F^pOe_!GYn4*pt}7@0w^jO{#$YhdN-F1
z4rvgf&@ih(*wYHmQi|#B3K9$2$?DP<n0osXpmeb4l+^4!5t`42KI&;tTd+7;i)f$=
z-k!tobmXZ$rra9moq{Q!wH#FjqmT?r2aBm?XpzEg&S!c1HClEPzB{WB{d^zG3-7_2
z3m_e$VjkyZvgX3AC5;a>$)MuKq&amWG1p0<jy77l7V{%VD6|7BVW(uS>p%h=m)Juw
zRUDL@IHkv2PZ3rG4xxd)w70F&TwIG=I+P~qdnLi+(3JjqtxBveeM_;pCU5^yVzT%H
n(wP1me70h&WqEsp%E<T$0KYgZTu2Fp00000NkvXXu0mjficwgB
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..156d9846dd2550d3c98306fbe8ed23d92fa9a725
GIT binary patch
literal 702
zc$@*g0zv(WP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00001b5ch_0Itp)
z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iXP)
z6CVfuPrMue00KTqL_t(I%Z-vjOB+ELhTr*iW|q2cDZ3&ojW{3yF-tZC4}u`}*0Z<%
z0Z$Upb14au+(Z!k3ks4`bJ2?%3tsXI>Y-qho1(;QFiw<APX<dAeXhg%d@u8T3~x{@
z7S&)d_|6!!0UVY}rP}j%>#eX5q8>%jm)Y6bED?Rn=kuu7>!)uD%H^`+oYxl@7iS#D
zfz}$SR0_6juVk}X#BqH3s$gqt%O#@5($dll=Ny-pmoUcQ_VyM^DM%?-qA0@2$;l~y
zNnlyl*B}UHeBa0U`8k9T5JI5YY~u0p5t&Q|L{zL)Ds6g6Ktv!SaLys6gl*ffZ5v@2
z!f_n*`+ekcIk>L-<HZJ?^WRrjS73}`YHAAIZg<$i^E^l?A*ICF*x08L!S?pH0`N1F
z$>8qp4nYv$=H><u4-dmO<KyG#bUJ9a+rQZJ@XpSTGRDMV7^;bh2@D1UNGaj_KD5>V
z0M~VqBngg=j#^slW$SsEh~g-Ul;?TST0;ndMx%jdvx)ind3c_OBuQ|5eB8RfzhB<i
z*tlQ-u)Djf0K~amPC1T)Znulc$w{1@ouSw36^$`H##qsH-498U{58g`6bgk40APE2
zd&(FS=ks|brNs61HBzY*eBVd4TE+VM`qPk)mSug`TL0PH-0TlOoG}(JEG#Hv3|g%g
z(&;pUAQ-Omi9(^!0ni!Q7!j$txjEEoHH2Y^>FH@492|@YUV{@6gb-L=UH$Kn*4EY-
k-T@~f?C<Z7T;hBA2R%v0bjV_-4gdfE07*qoM6N<$f<jb3DF6Tf
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..b5e907a99f19128410cf0328380b3eb8cbd4e3ba
GIT binary patch
literal 819
zc$@(<1I+x1P)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0?0{3K~y+TrIKAt
zl3^6bPu<i_-PO8j^R|mFx@xP7yy+&_mFuc?*Hv4$ZfzorR5U~<_(8-JrZz<!Fc)7b
z2~PY(nI8$X0X0#OSuukZ`10?0USDf&T8sYM*?G@--siV-&U5H*lFQ{)_4W0}hK7a#
znM`(HO8=iYw5h2nX}8<q^?K3Q*9V0{A(xITlS-wkYHn^$PEJn3@At#$bRrlG!eX&N
zsZ>@5Z*6V8z@Z#CJv|Mp)e2rU_lt{*7#kZyV`JmL!&$Wtc-!mi>mtPAaEKLawOR}h
z4`Xd@4K|jyJb$PXTF6{h(lrpBk*=RE4mO!gh(sb7A0LO??FJu-&*u~4P$-0CEP`Nr
zO}_M+K9pOiuKbkFN!L%8Mx(i|R;v+>MzOTCgsG`1%*@PSVPOH`a2W45Hn1DAW4Grj
z_TP|j&`qJToNoQG>W+?%Dh5rsTrRAvtYB+v3#+TEVwHRZu~-b-pW^sDQj5$emEY+I
zSt|)bI~gIT^AhH`j-Y(CmY0{sYB@BON+BMPi<Zr1u^k*j)^w$~;~?C*K}a)yFhpSn
zu9i^0#i5;@oe4fi4xOEy6}`b=fZ1$DCX+!vpGPXXj7ay*Z*UTJm{oHy^E?*1$-qW(
zSwbE{*^4Ivfq>XDU$f24O&E<vz6o+>bvm7{VCwIOUHdrmedO#m0))Ju@Rp6Dz&wwO
zqx7--itdWD?(OZZVwVzp`FZOej|Us<ank8Dy1Kg9KZj!7T%2fobi1fOtKD~zfLX$q
z3Br?F!dK>boO9EG@RBs;XLN};bbfvwb8~aB*=*wO?(OY~*3;AT3(QrPQBARflJFi@
z_`^ro9AK3)&trH9B`qb`IPQ^NuSYJI!{FecxJvw+=Z;KbiV_s0E-5KmYNL2bbAs_a
xI~U~ig81VU3Wa0Fa}uj^@drA|q~i)j^b--WOCfKLMJWIP002ovPDHLkV1obUep3Jd
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1e432f57d1d2a4afc2a7ea1789e12cebe3a58744
GIT binary patch
literal 615
zc$@)e0+{`YP)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0sKisK~y+TrISf(
zno$&nU3J&~2Dc65r*zrh(Op+ghyx-b#wigMr7lDi6+w-lbwJ~+I7MCLlR97!7cO*H
z-rjTjStz85+7}L2?>*<?OrPW}DV0j8OeS+H6pC+hx%`uO{eL2;R;!)GVliYg8F;;3
zs8lMY_<Wi4dc9Pq)6KHkEDD7JlF1}0l?p<k5Y%e*WpIPR@SdOqER{+Khr^((>1;F_
zh(sdLXf)@;S+<|4?7?8bA@O*eXMA&WgJ3X-e!q`sGz!+HFJj1PBCyZr!+1PKB9TBk
zod&fem&@`0tyT+yAi!$1ZpDz(WHOnqjYcCTlL?y5Ci3|_%H=ZZ^*XxUE=HpfmdhnP
z9?wrP<ZYSFW+{UTsZ<J`P6vy{0=-_3XGtxXPN!I{R=gUPz;`k1bseF!TJ3h5XG>7Z
z?&;}?+h()jr^Dg+LH=UszeP?Ki$$P567=EWfuAmy3x2;J&(F`;@AsI`=eWDOI~~d<
zE)<JJUNWuO<KrXTZZ~a$lG#@_r~?g@`Oc2e?$s4uG7fb*ol+Jg(DGC1_xJZ04u@E;
z*Ra`aznz#^H6QGD`$zUQz7wxr2NSebtD#!0^4+A}-EOzsSRem@&(`hjEq1#d0)YTu
zCHhTwM-$!77Y*ldIQ(UFCq7>;8qiB7K3^z_<QFDc)&Ab*384T0002ovPDHLkV1k?-
B72*H@
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..8125f28c3edd235256dfd2c9d164950398d77b0a
GIT binary patch
literal 604
zc$@)T0;BzjP)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0r5#hK~y+TrIWd;
zT2T~+t<ojWV4EV3a?3nNx>Qbx10o{M11d@^L==?^PM~qXdBj-@kHi5*EG)DOf6m&R
z8we!#s`+8Db?>!4)^tQ~NiLU*B@&58CX;=WN~KT2_5X>WYPEV5kH?YCX5sVspin5}
z!t-O&YPDjGMzhN0awryyNG6l0R;vhy!%!-fkHK|1-Ft!(uv{)95{ZDarnA**AsUTB
zrBdAw=d=AxWsgQ921z6mEMv3Tj8G_q;c$ppEC#+!Uxbj`L}0()kJ)U7=jUgn(`isk
z^7%ab-|2L~aU3ib%Si~iO$LMElU}dKd_G6J-A18MK&4Vav)M$y-^XM!!FIca*X#W$
zguE@I(J1DjTq>19uh+w7v%z36U|CWN7K;UTyB({BPvE-{_PSo6v|8P6mt{*(%5J$_
zGCLlR?6lkMKgeGQ{kO=;WHNEoM}pRBHFmn)ZUh1W91aIuE*GrVYq(sl+oAl#xl*ab
zN~SfNPN(pAJhTaN-oEmKy3#;--_=b}hr=P}gE(4#Dm|0QU_2gUzu&`ZwL)+c@zs2=
z*=!&AukoF5^|}b!Xf#l-*V%5;?w(F3W_%xigYTBp>BRYbMlcv;t3<!)?!^SR`$faK
qUax-`-3iZ+iw5*J6P_QGNc0P14ZK<qepIOd0000<MNUMnLSTX@>k(K0
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..9c8de22de1029785189e624643a4778980601516
GIT binary patch
literal 640
zc$@)%0)PF9P)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0u@O_K~y+TrISf(
zT2U0oU3Hg!2De@G8{{i=*?qr3SLw=$;DCsT8a0Xrr7lDi6~PI#4#YVs4vD((NgPnb
zg$t#d_TP6d&(H>IQu~L))qBtRai%AFNpiVdERjf@GMVhFR4V--T>qa4s#2-O;cytK
zR0=mYH&7@Pa^d+jX*3$KTCE<Z(`n>#ImBWy6pKap{eE0tUY-WmYPDwsC19aYKp+qR
zWld+TRzoltgi@*ecQ~KzM=HD9?J`It5@8veOeXkzK6E-AghC<kZTc*PyiNpmyWQyb
z`-n!PNF)-VmSi#+_W$ng4jjk9Y&Nfjkk?7C*T2{4bQlZ<sMqVrX0y1xy+yTJMXS}q
z{rx>=vl(12*AF4&Wf=?xF%RY9@i>~zCZ^LV+U+*Wl3Fkv4l$q4Sv7nD--NK^+Cyoz
z8jS|amY|f~Xf$HBTCLb=wOSvO=1=FMi=Stri|=4|NCY(+jU4rnprulYoeqZs9*+mh
z<r3TN7L&;YcDwzN{Dn}_U%}+WPn^r=^Q>fAvxkQVIGs+~1UYYC_(APxpuF$+3AzV9
zH<Ha}6Z1hFEkBi>OeWFm^{`khaCLR{lm%bSTZ_f=j{h3p2v^6!1g%smD3{A@H)(g*
z>oqgJkB9sAT)zYNy1u@~X0w6U>t(A%zv*sog4=N&0@HAIyWJl~cf#}Oq5&N;;rT>~
aM85zx@$O}Jmf1%D0000<MNUMnLSTaKq#zpr
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..95df0702ae44ca263e133d74e03d71d4a2efc6ca
GIT binary patch
literal 730
zc$@*+0ww*4P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00001b5ch_0Itp)
z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iXP)
z6CN=r?ij=X00LS`L_t(I%Z-yuNK|nY#((F3Z_YT*ORdnEbS{NiQPCUI63K*;9z<Y-
z60`^`f+%QFM7fA4DuSq0x{2Df>#>Q*F@uPp=!ue*I+mr7=p@bLT<^a{p^_7NmWT8E
zIGpo+!W5Ns=UT?7a{_RU8tRri`TZ`_t)x5GO6Dx-vTGW(U4L7TlpfQGtl6g~&Zz&v
ziR}3no=}Uq=Td|`6Z-F?%2#1-JkyZy6^@A*Y2nO(PiFX^ne~kkMq@ZHuYsL{ul-1A
z8HRUF#qr*EoKAj!lzszu-X^tZR|G4F^P&mK3?YHJIG;M9`2kY70<-!;Ln6EQkeQAb
z!vHe~9uIiuAl@)|XJS+>gMlIRk3poi4E22Z$+QBPM!VB~17pT+)Vg|zy#enm@aMt;
zKN9eR--Mi^;3R=W{z?mR-7%|=qQ7(^YnP(^ei;6V_<RubL17N`j-tCCw#r0!q;S3E
zI8M~qamtcN0PGYJm<>w{U|<*=4VDkSY3w8WdgMo}+?WV!t)s^7bC!fXFxZFqLfDOG
z(NEe?HC3=M3wj;w!v}k0=u>${>~WX;flDlHxTC7>q9wi*d~QdUmtkKy4+HNTfCSJW
z`KvQ^->Ys(#VaymPrCr*SMhcWJ03OmUbRsD@U{i1tb@fNQng!fe`pqfM7YQ=S@XgU
ztsgz_e~ygZTkGMdxxdLG-huPvJgVk0lobB;>G+8<zjVZ*BR=V2C}qjc0-OU|QQK}J
zRYjyWSN|1IQ|84Kae-H%uD>2PlDKfS-IM7A&csyM8+QEDRnt!FZyv$LNUg?PtN;K2
M07*qoM6N<$f;(tYTmS$7
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..9da65a84e2165c1247fe61c84d346cd7b74bc282
GIT binary patch
literal 749
zc$@+40uud+P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00001b5ch_0Itp)
z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iXP)
z6CMZWRE=E#00M1EL_t(I%Z*b_NK|1IJ@@;WH{;BEGozr%AoJ8DMWiV!haekVq(}u(
zNl+jXE!wt@nu6$OUAS};1VTiAn;=Ic$O?)U>5#@){-utn<L}J8d0&eXwG=(ud(XLt
zd(S0ogZ^lxsVuq*f<~k|t8T`hudmZ~WG0>6`Qf^t&~+>h4q|@bTeRwW-;LECz9so~
z$Yerx7acnu)CvU{jl*451tyKi#p)~`bWQebDG<NAM`z^tP}F!PD7<M{sm}mxWM-0Z
z?+$@UJ#wi!gZr;1dc>yV$<^cvlm(r;ijf}c1PKoq&qJzj2-$^?@YOT|kkR-(Hp@2S
za}E%35IF&<gK+~SyeK$)2vYaK&dnqLKr<ZP{A-&!U=AK%NhV<DoRHoy?DPnTc_4%b
zQZEMy10;2Dx7YWdK!1DKR5|;ZzxD{AMj?%Zu$FWvYY8NDkTgJA2{N-&$jpwt*>J{0
zd)UkYG5@{>GcQmCr7V#0fjl)Jl?0%`oF%a4k{Ew=XP8z}^?W_qmK5{XHk+>E0w`+^
zuzi?(_ZZ7RKfzso0z$7qHZ_G`UAKm5ajw4fa_<lUK%ygTD#c^|&`DD(aKKKFK^S{6
z(ftVa@=O#8%ZgZ3cnV$C!qg8!Hd5NwI|Klf=!lqLxtKq6+SGhmSPOAT|50#HB}SfH
zL22t-0)T;sb%wOHz@l^ddv(j#%&K!iG8glO&YRLbjm%6hw8B#m-r7HL;A<<YxbQv&
zAocIk1q3ra<xQCX(gH2e2H|hO$kW!pRkq!)K@evk?uN1d<ysIqL-1^jumcF<45Kg3
fZA>y}*eQMkcO%GeB_#9*00000NkvXXu0mjfvp+-x
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..bfb4f372c22f0a0ea580319fc2b02cfc52bb5ed8
GIT binary patch
literal 797
zc$@(p1LFLNP)<h;3K|Lk000e1NJLTq000yK000mO1^@s678qX}00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0<uX&K~y+Tjg)Oj
zlTj4M&xRvin1tjaKWLOCld>?V{30faVv7bPL_*&JsRe0;)WlU}^&u$eRm#MSz?2fS
zzy#Akk{~L4e_LA#x-rvd+cRJ0PXFs3tFa0G;m<wyKF|5>oO}0NTCFCV^&72kQjNbV
zH^noN23Co6jOqZ9&PSBDKxABcc}W)nu}T!C-s;2@TVvLY^sSC<b%h>VQQR>=VgwA1
zL!1lPoq3{CC1qs^*l<(6YrM_KqwlNu^(g~Sn)b?5TQz#Xw1A~A5?u$Z5zs?QT4XK5
zWczKF1}?MhX%0I~g)AT5$M!wya=^CuiHhMi0Bh!mb}Fgi`X46SZ?QaTVX3Kzr?V1R
z(rFgGTB-!_I6w`k>?@#Um?tRdp$dc9uCcejjO7PMSW1pr@TurGs(A<43wQ{?&RHS@
zwjf$T{8L1j)y&?mVs;vI@~oOBB%*l$#sRfrbO2b5I87Xqf(a@j>_<6!21?m(PMPsV
zQ>&Lq@(?Y~01B`S&=}f1hjuq8n4ngKja}r?hdZX+I`t))s0T1NK(lT@#fS<!P+@LJ
zyWE}?^|LvDI1+5QW|wOsQ*#hW_e7gw&)_it=kRBq!rCKvTS&Vj^{E*{E!lo&pCu3i
ztKIGDJ)=znEDx=<0@MpXLyPs8PaqzIPfruAS5QF>)+V2Ib>C%c!vS`DHwTtsU#`Zc
zU~l4)R03!h#^)H13JAt-fLri98(yQJf@-MP-s$as%gzrsxaX$c-&d7BB4FYfUe+V|
zJ|M3EIDvT*_A(P|kMV_AFy9<@y2kx$JulBEef(@$XZlp8&1kQnDCH3$u>grr04ouD
zC91(mCt`jQaRI#I9S~8d%mnBaBxa%#Ev!Nu1M6W?-QOQyl>jp!F$Wgc*7B955^!tS
bmqGLgz~{6BRIYwu00000NkvXXu0mjfmg!hD
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..7099d0039b48d34e8799920e4e737e7f85f11042
GIT binary patch
literal 405
zc$@*10c!q<P)<h;3K|Lk000e1NJLTq000yK000mO1^@s678qX}00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0V+vEK~y+TV;~m1
z+@d9Me}hKg-Bn8bP$2@K^dB#jg#oV4lU9AW*8bzC%gg@X-xB}%;v`91s1)e{WZ3PE
zo^PM-3H*QO(1QQ7nv?(EUl#i5Ot~PDE=2+`!@fQ0{r~A^&Hwvry#C+aJN5tD!+rmc
z7qR(4l@SM!4g2<}^Z&Pp&Hq1MFZh3NrOp4d^|GH%W-&-Wl@bQn4Ey?^_WzfAW&dw4
zGJJckiB}b>lrR_}hW+1f&+>SS=WVJO1_F0hX&B$y>iFmNwT6Gh7<NHR>pwOOm1bbL
zxkw@J?a7J2bmsK`)uoz$gj|NrFc5}HGcc^FS3mUi`nLbC548Wk(xLU|+ARG$BzqGX
z1X^-`SzaQ4_;dqb?uiO6<NfsvEKsRo2N)OtF{~?M>m3;700000NkvXXu0mjfr4+j#
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c325b0fa2743e6f6ac1ed8a98cfe6c94d92bb94f
GIT binary patch
literal 379
zc$@)y0fhdEP)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0T4+<K~y+TV;}{*
z+@d9Me}hKg-Bn8bP$9~})p^pY57*j%{B(KQ|NC3wKVO_AX$zGiAAk(Kz0vdS(>;Oz
z?;Kk2e^zty|NF~AKb<KTB;VyQL%%)h{r~A^&Hwvry#C+aJN5tD!+rmc7qR(4m5~OJ
z4gL10^Z&Pp&Hq1MFZh3NrOp4d^|GH%W-&-Wl@brI8T$1>?f)<L%KqP8Wcc=66R#>%
zDe+*08~T60J<H=Qp0}xLC<xqHrD1$!tJa^>+v@*O%}@}yxkw@J?a7J2bn5i~)TWw$
zR526;*3_#X`g(oa|JMiF|6i%s`g3iT{vC=91%W_I?k~$r<PV>2;LANx!DS2;9x;G{
Z0RZ6|4ppka6R!XO002ovPDHLkV1n#Au?_$L
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a4084ea806d97603b9c51167d2500692a3a48167
GIT binary patch
literal 412
zc$@*80b~A&P)<h;3K|Lk000e1NJLTq000yK000mO1^@s678qX}00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0WnELK~y+TV;~m1
z+@d9Me}hKg-Bn8bP$2@K^dB#jg#oV4lU9AW*8bzC%gg@X-xB}%;v`91s1)e{WZ3PE
zo^PM-3H*QO(1QQ7nv?(EUl#i5Ot~PDE=2+`!@fQ0{r~A^&Hwvry#C+aJN5tD!+rmc
z7qR(4l@SM!4g2<}^Z&Pp&Hq1MFZh3NrOp4d^|GH%W-&-Wl@bQn4Ey?^_WzfAW&dw4
zGJJckiB}b>lrR`ZhJlh<>5u<lj1K<qw`Y00#q&1FF2iNuomCpfx3)U|d3~+nA5n(E
z3_`~+aC4DD-rJKCf$7ZY|Eo(i|A=-OI);HY_3DSdUf=fr^?~;PS30!*T$`nTho}fZ
zHwXp-ExEreFOffdx`8kEL<N`e{(1%$s1$yH%fLYl7#IL`sY9-g?o1y50000<MNUMn
GLSTX~$-e>s
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..682a501758e923dd75a95214c1f9c3fc33366d21
GIT binary patch
literal 466
zc$@*!0WJQCP)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0cS}>K~y+TV;}`g
znKDIU^5n^Z9UUF~P$9}eQ&W@btXZ=@o;!E$|NQy$KiAgQ+Crts2OvWiE?oF_*|KH-
z_wV2TzoDT4Wca7@@^bQB4m0%3nKS<n9Xj-X&YU^_ckS8*wjet@+YhRYG=OaA>C>nG
zpFDZ;|K7cO|4*AX?SFlJ{im#~ED5Mm;sG{8j~zSq|M20%|A7(swzRZV6{?hYK#ZY|
z?Q86uI@Z-Ywyxa|rh$B@cI<!{LtzFwcdz>o!!Qe=#-IXXU5acd2p}7d;c{RsvjCIO
zZ9-uRauv)Vbb~<{!_^ZfPBd<8Z2Yro)vAB^A__eS&<zI3LzO}RFq`Iq(kU=^{a><V
z$v<p{f&jXUojZ`&K#bwy!otEs*REarzkBy?P#XQy)6;VY-B1u9IgBkVEWTu9WE?Ij
zDap;t%QFTV&H|Of03cVP2MLIe;aYM4$Yn4Kz%;r`X#*G-08`7cwB1dl@Bjb+07*qo
IM6N<$f>jaAzW@LL
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e6e3966707b9196043d312f42a32f12325ed8410
GIT binary patch
literal 476
zc$@*;0VDp2P)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0dYx0K~y+TV;}{*
z+@d9Me}hKg-Bn8bP$9~})p^pY57*j%{B(KQ|NC3wKVO_AX$zGiAAk(Kz0vdS(>;Oz
z?;Kk2e^zty|NF~AKb<KTB;VyQL%%)h{r~A^&Hwvry#C+aJN5tD!+rmc7qR(4m5~OJ
z4gL10^Z&Pp&Hq1MFZh3NrOp4d^|GH%W-&-Wl@brI8T$1>?f)<L%KqP8Wcc=66R#>%
zDe-_9Lmk`K*g18qt9NW&yB|yg`B3fH0WpTc40P^Z_aBB~7C?<b1;n}(*-#KbHXOs{
z|M%OoJl^7Yn^2g7Tm>@--Cz*LaP^&48pc<)YW+FAt^Oarh(Zqnbb~?iP^A!XbCE*c
z+mjQ4>D1}}sZBNiuo(&h=q`5dKw<+ihKtwKs~`G$ecS)n2ipH%sn_~*ZI=EWbVEUa
z<S-7j<o>d}ME>yU2EN=A6<o$pVXOe;D)b-$@iAOW4gk3fW&xN+cPVWE0|NlU?ys4K
SFl4U)0000<MNUMnLSTZEh}?Ss
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..54b0e141f76ea34d2211bf82e1a781cc8ef863fd
GIT binary patch
literal 444
zc$@*e0Ym<YP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00001b5ch_0Itp)
z=>Px#24YJ`L;%nLDgY{;*UrWO000SaNLh0L01ejw01ejxLMWSf00007bV*G`2ipN3
z5CR59L`?Dk00BHnL_t(I%gxfUO2a@L$MIi!ZKZT;J6M8*19eG3&_xM8fV=M@1ov)T
zDuPQPuaLn<5DL~f#&(ck2Pq9)mVz|2;gY*JwIV89Hh<d<pYQPJG!&TQIJ=zlvouW~
zY}@_-fRZZi^?C<{kifF6dtKM(-EQ|B05nys>-ux0Qfai?Z48G)7>2PNkH>pN6`Q8n
zsMTtiPN(Q}I%qbVJCn)8(^PSu=a5n&j$;HtfGCQVjIqN~5iEp2wOWM`VzFASP8`Q!
zS`jRSfbaW@*=%;&@Auz+Jz^33BTy873%IUZUa!}$rfKf0;w$_5>m!9>cm#k-DRULP
z**3sAw<x8=_kAr*(_97L-QJ+Qc`(K<Tdfu-r5KGyk`VG9MbX7yc(ZN5^St9ENgnI<
my309#%ChWbFc@U`_xl7wF$1$>zHa#d0000<MNUMnLSTXoyStbG
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..cdf915d5e4df91e68118a7614585b9da6be2ec63
GIT binary patch
literal 646
zc$@)-0(t$3P)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0vkz0K~y+T)sxF>
z6Hye#KcS#IS3cH41;LGU6BM;BbfF-Ke}LOyXkZ{QbumnmRn(*<CJh)<LTJn)x^dwv
zF$NQegv6+uh*W%SDoW8dDwvsjkLSi|Nu_OUUHQS~aPGP1H{aX`HllbuzB?9+oe70P
z+wG};S1=eHP9~EJtyYWD>2yce^}}{)^HR`ECNrPQ<)~aPli%-G@I{}`w|VDF&}Oqq
zl}d$DsT36oh295TF4t*$*~ZilZ8REGuh*$mDp4YlAiu6Jw#N4y5g}w`5c>$&Vz;fY
z)u6RnjjGiu@d3_HXy^9JOYoUF%*qfh$T4iSTi4bg=(wisl>r>GrtqwD2ZG~MY2e_x
zt_MNAUhgm&*d`*F{&WkX90PskC?=3n^EPIs{syYnvD;(T3|`FL1|2;B`pD6`f#(b!
z?ScBmr#!@0Xm%j<B6f*5ySodT<1wnh991r+kmN1d*#j;q8jX$w0s$+Z&&#!-Y7b=!
zC%SojpbF^wV^EQ|;s-~ScW>goypH25_L`rmTisKBU6jpc<)2U`<wjh?Wal0PJpp}r
z2s(cj^p&H^7w*I2dW8RKhus2bnpSDI+f*zT$uJBn91g$khbl=Or@E7PM}^-Ge?9`e
zoMERNRZb5;=2g6911Iekz~OLwb2^<>kH<6ScDqLyK4h1cCH%Nz2Jy-a<Bb_vWvF?s
g3j>$!mj6b;FQ&-AtsZH$djJ3c07*qoM6N<$f_i5iBLDyZ
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2a70ddb6b0398415db87d3fb6ae856d3970fa8f9
GIT binary patch
literal 383
zc$@)$0f7FAP)<h;3K|Lk000e1NJLTq000yK000mO1^@s678qX}00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0Tf9@K~y+TV;~kx
znKDIU^5n^Z9UUF~P$BX`Q&W@btXZ=@o;!E$|Dr{UKG)XP+CrsB2Oz^1E?oF_*|KH-
z_wV2TzoDVw|NQy$Kb4o4lkQTOVQ0>q`G4rpq5pH{%=y1-*DkQZ+1c5CP-VmcWW!FM
zKK=jX$&>&0?%n%;+O%o^>+9=3Wo2baK$Q{(*bF;%?AZUqhY$Y;M$FsN(o$8ZQo>+_
z7zT_r7GP4iO%=mHVB*Ay#=wO82WY@QVhp>WrS%^hhDtLq0JBpbD4hWf1tpDtgj|Nr
zFc5}HGcXhu79P5G?b`p{yLbNwhUK50o}N1-dlMO0SXg|?$jCTcQc{wemzQS@Ot>si
dsbL2g7ytn@D+d_(rHlXo002ovPDHLkV1l}3q;LQL
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a1c3a3528f20175d88862d654c78fd0226122238
GIT binary patch
literal 369
zc$@)o0gnEOP)<h;3K|Lk000e1NJLTq000&M000mO1^@s6677#B00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0R~A#K~y+TV;}`g
znKDIU^5n^Z9UUF~P$9}eQ&W@btXZ=@o;!E$|NQy$KiAgQ+Crts2OvWiE?oF_*|KH-
z_wV2TzoDT4Wca7@@^bQB4m0%3nKS<n9Xj-X&YU^_ckS8*wjet@+YhRYG=OaA>C>nG
zpFDZ;|K7cO|4*AX?SFlJ{im#~ED5Mm;sG{8j~zSq|M20%|A7(swzRZV6{?hYFv1N5
z#xe^q3Eifup&&4E;zZ-d#>PLZR;~I+HA6uFm`(FQ=@gi|{x4aw<R4WG1%blC!b8`t
zUHiX#_ij)c{nOLabBAI>LBPVo;!8$G#^I8ZlH9z!JY%5YEKsQt0T>tnarY2#bh?ah
P00000NkvXXu0mjfW?iGm
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..b7a69a0d68f7a0457b3aa0fc2ad386bcdd226d55
GIT binary patch
literal 392
zc$@)<0eAk1P)<h;3K|Lk000e1NJLTq000yK000mO1^@s678qX}00001b5ch_0Itp)
z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0Ub$1K~y+TV;~kx
znKDIU^5n^Z9UUF~P$BX`Q&W@btXZ=@o;!E$|Dr{UKG)XP+CrsB2Oz^1E?oF_*|KH-
z_wV2TzoDVw|NQy$Kb4o4lkQTOVQ0>q`G4rpq5pH{%=y1-*DkQZ+1c5CP-VmcWW!FM
zKK=jX$&>&0?%n%;+O%o^>+9=3Wo2baK$Q{(*bF;%?AZUqhY$Y;M$FsN(o$8ZQo>*u
z8CLp_m!b5>e=tS|z*u7eCWYH1y9}3s6DLkI1}5A;Km+~}Wf;sLbPNN)?34#eXFx+i
zN#h^UE<?vKP*_-a=-Rbw|99`+{T~>Xe|ma)?hq9L=mx=ng@whJjEs!KB_$=fd3kxp
mz=X>JmBJ5j890am0|Nj>F+)>CIL#~o0000<MNUMnLSTXc6RCFq
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..bb7b212615bc1382b1fe9b5ade219bd9c97eb2f3
GIT binary patch
literal 435
zc%17D@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|*pj^6T^Rm@
z;DWu&Cj&(|3p^r=f!eQwFr$;k><XYDdx@v7EBj404q*wd^ZUcnfTkFGx;TbdoK8+r
zVEm&wang_PPiLzhUZ4N_#<Rb_P8<LNmo?9>3%*%ZZ~x(bido#2zK7FqPj1hfv&}{N
zgH==P)cO9uS(N7++<!lvhv&N9$<H1J(z$!H4xK#jvL-EUp1jkUUCVfSp1+bb*{drt
z<7{%Jz0tqz=_+ByK5yptu^!%^`&-iPe%;TXa*xeT(}GNl4S=Ao>hOp6XWPsD|J9fq
zTYP;ksH{0l(!xUG-<_I&@7}HcE*8~Ze<v*g2$r}Qw=pis0<jy~56-s}7yEX8&+(a+
zbsoa2B~@kr?5uE_|4_bLNpn^p?~lKyX9e2z{XGBpze~0oWAzqQzQ3{cpZ?F^H1Gd?
zAD4eMn}v54pPlABb%I@mf*a?DTF&WDY}PA$%Dmb?pXd0#;=27W3MM?{KfHjQVeOW+
V7Eb?OWdTE!!PC{xWt~$(6986uzkdJ#
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..f7f52a30b335af47efc8ed05ee7748a9c5e05104
GIT binary patch
literal 453
zc$@*n0XqJPP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00001b5ch_0Itp)
z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01ejw01ejxLMWSf00007bV*G`2iXP)
z6A~S^gF7n#00BiwL_t(I%gxfiO9F8i$MMgd)@k@FlTdIG)5Rj99~2cDSl}2$^q({d
z{S6JR?m=yeL*!Nw2ceK5M_J+EDJp4r-VeXuriLSk@6pmTJ<IDoJUkpMOe>!V4t26I
z*3|3h;mZdA@T|C+3&#{PTk}V=mx4RlD4&M5nFG~aIOYm2+}WKzPphY)Ao4Cxa4Rn!
zPkU{wIFLyDC#KvO)vpkVC*k#lg!W_cmSe?$0Z<M4u!BxT#Goz@)ORV)TfvlSunCi(
zRP7R7US2yxle2;;Q6b-#x_zm(zLf1t0KokP{|H#crT{Ia><z{FG&7%`W5uRFcy2cr
zIm$;U8B<nl3Xsug(;3*awZtMvbd|DT0I+{AmAY_{S&e}NcJWrYAe@7~s<M>%4V!Lo
vl=6M<uXrD#?%uhf7&nSzSl&5-fqy?=S*h$cTTNKZ00000NkvXXu0mjf(4V}g
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..4e51c94d1fa0dbd2aacafe0f5c0d8bdee3f4c214
GIT binary patch
literal 505
zc$@+G0S5kwP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00001b5ch_0Itp)
z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01ejw01ejxLMWSf00007bV*G`2iXP)
z6BZDxUV@SU00DVPL_t(I%e|7lPr^VL#-HoQB5@%M;6+FWCk7KuL}-k&yOaL_J5I86
zW9d%P#J_;S(SgB8A|i&NvMGxp63b%ZM-Z;<orAU^gg{w*lUv?<e$QRr_aRuJEXztV
z#`3P~UX@CvcL0#0!j(#8OP1wFUDq!tr7wnI8~^}`3R6n&R8@_qQYkbV4P-Kz&u+K7
zsYHR#%H^l&o?EX&Rn_%Qr*pUz7Jvv2mn89(P747D!!SOI#bQ!f_H6*TZ@0t#UM`34
zwKXvsjdm@|a>yb$o6W|B5a67{_kH9G1pt6ANv{g$8vvlJ0*hbCMCSY?&?`}3-}f=K
zvQq+F2TYiQTCJu~N-yWZwr#%w@c*Re(~pJ*gTY`MfW|o&vv9xP-zR`V078JtTm#0K
zu4$TV+ct4sS4_jEY4(Cu&dw~Hf#W#Gi9`aLrlHwva#@z2hr{8KX_~!7SqTkVt=7(X
vJidv=Vl~Ftt><|U)oRrX?n%Vh|9|}eXD!-lw%t!)00000NkvXXu0mjfpfuRR
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-win/enigmail-common.css
@@ -0,0 +1,415 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * Common Enigmail styles for all platforms
+ */
+
+
+.action-box {
+ width: 100px
+}
+
+#expandedEnigmailBox {
+ background-color: #d8e0e8;
+ color: black;
+}
+
+.enigmailHeaderName {
+ color: #888a85;
+ /* lower contrast, TB3 */
+ text-align: right;
+ background-color: transparent;
+ -moz-margin-end: 2px;
+}
+
+.enigmailHeaderNameBox {
+ width: 7.7em;
+ background-color: #d8e0e8;
+ color: black;
+}
+
+.enigmailHeaderValue {
+ min-width: 50px;
+ white-space: normal;
+ color: black !important;
+ line-height: 1.4em !important;
+
+ -moz-appearance: none !important;
+
+ padding: 0px !important;
+ margin: 0px !important;
+ -moz-margin-start: 3px !important;
+ border: none !important;
+ background-color: transparent !important;
+}
+
+.enigmailHeaderSpacer {
+ width: 2px;
+}
+
+.enigmailHeaderBoxLabelNoSignature {
+ background-color: #d8e0e8;
+ color: black;
+}
+
+.enigmailHeaderBoxLabelSignatureOk {
+ background-color: #ccffcc;
+ color: black;
+}
+
+.enigmailHeaderBoxLabelSignatureVerified {
+ background-color: #ccffff;
+ color: black;
+}
+
+.enigmailHeaderBoxLabelSignatureNotOk {
+ background-color: #FFD8FE;
+ color: black;
+}
+
+.enigmailHeaderBoxLabelSignatureUnknown {
+ background-color: #FFFFC9;
+ color: black;
+}
+
+.enigmailHeaderBoxLabelBuggyMailFormat {
+ background-color: #FFD8FE;
+ color: black;
+}
+
+.enigmailLink {
+ text-decoration: underline;
+ color: blue;
+ cursor: pointer;
+}
+
+.enigmailGroupbox {
+ border-radius: 4px;
+ border-style: groove;
+ border-width: thin;
+ padding: 3px;
+ margin: 3px;
+ border-color: lightgrey;
+}
+
+.enigmailCaptionboxNoTitle {
+ border: 2px groove threedface;
+ padding: 0.5em;
+ margin: 1em 2px;
+ border-radius: 4px;
+}
+
+.enigmailCaptionbox {
+ border: 2px groove threedface;
+ border-top: none;
+ padding: 0.5em;
+ margin: 1em 2px;
+ border-radius: 4px;
+}
+
+.enigmailCaptionbox>h1 {
+ font: 1em normal;
+ margin: -1em -0.5em 0;
+ border-radius: 4px;
+}
+
+.enigmailCaptionbox>h1>span {
+ float: left;
+ border-radius: 4px;
+}
+
+.enigmailCaptionbox>h1:before {
+ border-top: 2px groove threedface;
+ content: ' ';
+ float: left;
+ margin: 0.5em 2px 0 -1px;
+ width: 0.75em;
+ border-radius: 4px;
+}
+
+.enigmailCaptionbox>h1:after {
+ border-top: 2px groove threedface;
+ content: ' ';
+ display: block;
+ height: 1.5em;
+ left: 2px;
+ margin: 0 1px 0 0;
+ overflow: hidden;
+ position: relative;
+ top: 0.5em;
+ border-radius: 4px;
+}
+
+.enigmailStrong {
+ font-weight: bold;
+ color: red;
+}
+
+.enigmailUidInactive {
+ color: gray;
+ font-style: italic;
+}
+
+.enigMsgHdrView-flat-button {
+ min-width: 1px !important;
+ -moz-appearance: none !important;
+ color: black !important;
+ background-color: #DDDDDD !important;
+
+ border: 2px solid transparent;
+ margin-top: 2px !important;
+ margin-bottom: 2px !important;
+ padding: 0 2px;
+ border-color: ThreeDShadow;
+ border-width: 1px;
+ min-height: 1ex;
+}
+
+.enigMsgHdrView-flat-button:hover {
+ background-color: #CCCCCC !important;
+}
+
+.enigmailToolbar {
+ -moz-appearance: none;
+}
+
+.enigmailTitle {
+ font-size: larger;
+ font-weight: bold;
+}
+
+.enigmailDisabled {
+ color: #a6a6a6;
+ /* grey */
+}
+
+#messagepanebox[enigSigned="ok"] #messagepane {
+ color: red;
+}
+
+#expandedautocryptRow {
+ visibility: collapse;
+}
+
+.enigmailMessagePane {
+ margin: 6px;
+ -moz-user-focus: normal;
+ -moz-user-select: text;
+ cursor: text !important;
+ white-space: pre-wrap;
+ unicode-bidi: -moz-plaintext;
+}
+
+/***************************************************
+ * Various other styles
+ ***************************************************/
+
+.enigmailDialogTitle {
+ font-size: 120%;
+ font-weight: bold;
+ padding-bottom: 6px;
+}
+
+.enigmailDialogBody {
+ -moz-user-focus: normal;
+ -moz-user-select: text;
+ cursor: text !important;
+ white-space: pre-wrap;
+ unicode-bidi: -moz-plaintext;
+}
+
+.enigmailDialogInfoBox {
+ border: 1px solid #afafaf;
+ border-radius: 5px;
+ padding: 15px;
+ margin: 0px 10px 0px 10px;
+}
+
+.enigmailPrefsTitle {
+ font-weight: bold;
+ height: 25px;
+}
+
+.enigmailKeyImportHeader {
+ font-weight: bold;
+ color: #888;
+}
+
+.enigmailKeyImportUserId {
+ font-weight: bold;
+}
+
+.enigmailKeyImportCaption {
+ list-style-image: url("chrome://openpgp/skin/importSuccess.png");
+ max-height: 2em;
+ max-width: 2em;
+}
+
+.enigmailErrorIcon {
+ list-style-image: url("chrome://openpgp/skin/password-error.svg");
+ padding-top: 5px;
+ padding-bottom: 5px;
+ height: 2.2em;
+ width: 2.2em;
+}
+
+.enigmailKeyImportDetails {
+ color: blue;
+}
+
+.enigmailKeyImportDetails:hover {
+ text-decoration: underline;
+}
+
+treechildren::-moz-tree-cell-text(enigmailSubkeyTitle) {
+ font-weight: bold;
+}
+
+treechildren::-moz-tree-cell-text(enigmailOwnKey) {
+ font-weight: bold;
+}
+
+treechildren::-moz-tree-cell-text(enigKeyInactive) {
+ color: gray;
+ font-style: italic;
+}
+
+treechildren::-moz-tree-column(enigDontEncrypt) {
+ background-color: rgb(90%, 90%, 90%);
+}
+
+treechildren::-moz-tree-cell-text(fixedWidthFont) {
+ font-family: "Courier New", Courier, monospace;
+}
+
+.enigmailExpandViewButton {
+ width: 9px;
+ /* The image's width is 9 pixels */
+ list-style-image: url("chrome://openpgp/skin/twisty-clsd.png");
+}
+
+.enigmailCollapseViewButton {
+ width: 9px;
+ /* The image's width is 9 pixels */
+ list-style-image: url("chrome://openpgp/skin/twisty-open.png");
+
+}
+
+.enigmailWarningIcon {
+ list-style-image: url("chrome://openpgp/skin/warning-16.png");
+}
+
+treechildren::-moz-tree-image(enigSignedEncrypted) {
+ list-style-image: url("chrome://openpgp/skin/col-encrypted-signed.png");
+}
+
+treechildren::-moz-tree-image(enigSigned) {
+ list-style-image: url("chrome://openpgp/skin/enigSignOk.png");
+}
+
+treechildren::-moz-tree-image(enigEncrypted) {
+ list-style-image: url("chrome://openpgp/skin/enigEncOk.png");
+}
+
+/*
+ the following styles are available for the key trust
+ columnm in the key manager:
+ enigmail_keyValid_unknown
+ enigmail_keyValid_invalid
+ enigmail_keyValid_disabled
+ enigmail_keyValid_revoked
+ enigmail_keyValid_expired
+ enigmail_keyTrust_untrusted
+ enigmail_keyTrust_marginal
+ enigmail_keyTrust_full
+ enigmail_keyTrust_ultimate
+ enigmail_keyTrust_unknown
+
+They can be applied using:
+treechildren::-moz-tree-cell(STYLE) {}
+treechildren::-moz-tree-cell-text(STYLE) {}
+*/
+
+/******************************
+ * Rules for filter actions
+ ******************************/
+
+.ruleactiontarget[type="enigmail@enigmail.net#filterActionMoveDecrypt"] {
+ -moz-binding: url("chrome://messenger/content/searchWidgets.xml#ruleactiontarget-folder") !important;
+}
+
+.ruleactiontarget[type="enigmail@enigmail.net#filterActionCopyDecrypt"] {
+ -moz-binding: url("chrome://messenger/content/searchWidgets.xml#ruleactiontarget-folder") !important;
+}
+
+.ruleactiontarget[type="enigmail@enigmail.net#filterActionEncrypt"] {
+ -moz-binding: url("chrome://messenger/content/searchWidgets.xml#ruleactiontarget-forwardto") !important;
+}
+
+.enigmailPassphraseQuality {
+ margin: 2px 4px;
+ min-width: 128px;
+ height: 12px;
+ background-color: #C21540;
+}
+
+.enigmailPassphraseQuality[value="medium"] {
+ background-color: #5885C4;
+}
+
+.enigmailPassphraseQuality[value="high"] {
+ background-color: #64C4A1;
+}
+
+.enigmailPassphraseQuality[value="excellent"] {
+ background-color: #1A9C2A;
+}
+
+/* a spinning wheel circle */
+.enigmailWheel {
+ list-style-image: url("chrome://openpgp/skin/spinning-wheel.png");
+ max-width: 100%;
+ max-height: 100%;
+}
+
+.enigmailSpinning {
+ animation: enigmailDoRotation 1.4s infinite linear;
+ transform: translateZ(0);
+}
+
+@keyframes enigmailDoRotation {
+ 0% {
+ transform: rotate(0deg);
+ }
+
+ 100% {
+ transform: rotate(360deg);
+ }
+}
+
+/***************************************************
+ * Styles for Setup Wizard and Key Generation
+ ***************************************************/
+
+#passphraseBox #passphraseError {
+ margin-top: 3px;
+ margin-bottom: 3px;
+ color: red;
+}
+
+#passphraseBox #passphraseErrorRepeat {
+ margin-top: 3px;
+ margin-bottom: 3px;
+ color: red;
+}
+
+.enigmailOkSign {
+ content: url("chrome://openpgp/skin/ok-sign.svg");
+ padding-left: 5px;
+ padding-right: 5px;
+ height: 1.3em;
+}
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-win/enigmail-html.css
@@ -0,0 +1,101 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * Enigmail style for HTML pages, displayed in tabs
+ */
+
+
+
+
+body {
+ color: black;
+ padding: 0;
+ background-color: white;
+ border-style: none;
+ font-family: Arial, Helvetica, sans-serif;
+ font-size: 14px;
+}
+
+h1 {
+ font-size: 2em;
+ color: #0077bb;
+}
+
+h2 {
+ font-size: 1.4em;
+ margin-top: 1.3em;
+}
+
+h3 {
+ font-size: 1.2em;
+ margin-top: 1.3em;
+}
+
+ul {
+ list-style-image: none;
+}
+
+a {
+ text-decoration: underline;
+}
+
+a:link {
+ color: blue;
+}
+
+a:visited {
+ color: #9900cc;
+}
+
+a:hover {
+ color: red;
+}
+
+.header-bar {
+ box-sizing: border-box;
+ font-size: 14px;
+ font-weight: 300;
+ line-height: 16.8px;
+ margin-left: 0px;
+ margin-right: 0px;
+ padding-left: 15px;
+ padding-right: 15px;
+ position: relative;
+ min-height: 80px;
+ background-color: #0077bb;
+}
+
+.header-icon {
+ box-sizing: border-box;
+ float: left;
+ font-size: 14px;
+ line-height: 16.8px;
+ margin-left: 0px;
+ margin-right: 10px;
+}
+
+.spacer {
+ padding-top: 40px;
+}
+
+.body {
+ max-width: 800px;
+}
+
+.logo-img {
+ max-height: 90px;
+}
+
+button {
+ font-size: 1.2em;
+ margin-top: 1.3em;
+}
+
+.hidden {
+ visibility: collapse;
+ max-height: 0px;
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-win/enigmail.css
@@ -0,0 +1,289 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * Enigmail styles for Windows (Aero Theme)
+ */
+
+@import url("chrome://openpgp/skin/enigmail-common.css"); /* common styles for all platforms */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+
+#button-enigmail-decrypt
+ {
+ list-style-image : url("chrome://openpgp/skin/decrypt-active-18.svg");
+ }
+
+#button-enigmail-decrypt[disabled]
+ {
+ list-style-image : url("chrome://openpgp/skin/decrypt-inactive-18.svg");
+ }
+
+
+
+/***************************************************
+ * Icons for compose toolbar: encryption
+ ***************************************************/
+
+#button-enigmail-encrypt
+{
+ list-style-image : url("chrome://openpgp/skin/encrypt-inactive-18.svg");
+}
+
+/* hide label unless text-only mode is enabled */
+toolbar:not([mode="text"]) #button-enigmail-encrypt .toolbarbutton-text {
+ display: none;
+}
+
+#button-enigmail-encrypt[disabled] {
+ list-style-image : url("chrome://openpgp/skin/encrypt-disabled-18.svg");
+}
+
+#button-enigmail-encrypt[encrypted="activeNone"]
+{
+ list-style-image : url("chrome://openpgp/skin/encrypt-active-18.svg");
+}
+
+#button-enigmail-encrypt[encrypted="forceYes"]
+{
+ list-style-image : url("chrome://openpgp/skin/encrypt-active-18.svg");
+}
+
+#button-enigmail-encrypt[encrypted="forceNo"]
+{
+ list-style-image : url("chrome://openpgp/skin/encrypt-inactive-18.svg");
+}
+
+
+
+/***************************************************
+ * Icons for compose toolbar: signing
+ ***************************************************/
+
+#button-enigmail-sign
+{
+ list-style-image : url("chrome://openpgp/skin/sign-inactive-18.svg");
+}
+
+/* hide label unless text-only mode is enabled */
+toolbar:not([mode="text"]) #button-enigmail-sign .toolbarbutton-text {
+ display: none;
+}
+
+#button-enigmail-sign[disabled] {
+ list-style-image : url("chrome://openpgp/skin/sign-disabled-18.svg");
+}
+
+#button-enigmail-sign[signed="activeNone"]
+{
+ list-style-image : url("chrome://openpgp/skin/sign-active-18.svg");
+}
+
+#button-enigmail-sign[signed="forceYes"]
+{
+ list-style-image : url("chrome://openpgp/skin/sign-active-18.svg");
+}
+
+#button-enigmail-sign[signed="forceNo"]
+{
+ list-style-image : url("chrome://openpgp/skin/sign-inactive-18.svg");
+}
+
+/***************************************************
+ * Icons for compose toolbar: attach own key
+ ***************************************************/
+
+#button-enigmail-attach
+{
+ list-style-image : url("chrome://openpgp/skin/attach-inactive-18.svg");
+}
+
+#button-enigmail-attach[disabled] {
+ list-style-image : url("chrome://openpgp/skin/attach-disabled-18.svg");
+}
+
+#button-enigmail-attach[addPubkey="true"]
+{
+ list-style-image : url("chrome://openpgp/skin/attach-active-18.svg");
+}
+
+
+/***************************************************
+ * Icons for messenger status bar
+ ***************************************************/
+
+#enigmail-status-bar #enigmail-signed-status {
+ list-style-image: none;
+ visibility: collapse;
+}
+
+#enigmail-status-bar[signed="ok"] #enigmail-signed-status {
+ list-style-image: url("chrome://openpgp/skin/enigSignOk.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[signed="notok"] #enigmail-signed-status {
+ list-style-image: url("chrome://openpgp/skin/enigSignNotOk.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[signed="unknown"] #enigmail-signed-status {
+ list-style-image: url("chrome://openpgp/skin/enigSignUnkown.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[signed="inactive"] #enigmail-signed-status {
+ list-style-image: url("chrome://openpgp/skin/enigSignInactive.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar #enigmail-encrypted-status {
+ list-style-image: none;
+ visibility: collapse;
+}
+
+#enigmail-status-bar[encrypted="ok"] #enigmail-encrypted-status {
+ list-style-image: url("chrome://openpgp/skin/enigEncOk.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[encrypted="notok"] #enigmail-encrypted-status {
+ list-style-image: url("chrome://openpgp/skin/enigEncNotOk.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[encrypted="inactive"] #enigmail-encrypted-status {
+ list-style-image: url("chrome://openpgp/skin/enigEncInactive.png");
+ visibility: visible;
+}
+
+
+/***************************************************
+ * Icons for compose toolbar: Protect header
+ ***************************************************/
+
+#enigmail-toolbar-encryptHdr-button
+{
+ list-style-image : url("chrome://openpgp/skin/headerUnprotected-18.svg");
+}
+
+#enigmail-toolbar-encryptHdr-button[disabled] {
+ list-style-image : url("chrome://openpgp/skin/headerUnprotected-18.svg");
+}
+
+#enigmail-toolbar-encryptHdr-button[checked="true"]
+{
+ list-style-image : url("chrome://openpgp/skin/headerProtected-18.svg");
+}
+
+/***************************************************
+ * Icons for compose status bar: signed states
+ ***************************************************/
+
+#enigmail-status-bar[signed="forceYes"] #enigmail-signed-status {
+ list-style-image: url("chrome://openpgp/skin/enigSignForceYes.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[signed="forceNo"] #enigmail-signed-status {
+ list-style-image: url("chrome://openpgp/skin/enigSignForceNo.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[signed="inactiveNone"] #enigmail-signed-status {
+ list-style-image: url("chrome://openpgp/skin/enigSignInactiveNone.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[signed="inactivePlus"] #enigmail-signed-status {
+ list-style-image: url("chrome://openpgp/skin/enigSignInactivePlus.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[signed="inactiveMinus"] #enigmail-signed-status {
+ list-style-image: url("chrome://openpgp/skin/enigSignInactiveMinus.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[signed="inactiveConflict"] #enigmail-signed-status {
+ list-style-image: url("chrome://openpgp/skin/enigSignInactiveConflict.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[signed="activeNone"] #enigmail-signed-status {
+ list-style-image: url("chrome://openpgp/skin/enigSignActiveNone.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[signed="activePlus"] #enigmail-signed-status {
+ list-style-image: url("chrome://openpgp/skin/enigSignActivePlus.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[signed="activeMinus"] #enigmail-signed-status {
+ list-style-image: url("chrome://openpgp/skin/enigSignActiveMinus.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[signed="activeConflict"] #enigmail-signed-status {
+ list-style-image: url("chrome://openpgp/skin/enigSignActiveConflict.png");
+ visibility: visible;
+}
+
+/***************************************************
+ * Icons for compose status bar: encrypted states
+ ***************************************************/
+
+#enigmail-status-bar[encrypted="forceYes"] #enigmail-encrypted-status {
+ list-style-image: url("chrome://openpgp/skin/enigEncForceYes.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[encrypted="forceNo"] #enigmail-encrypted-status {
+ list-style-image: url("chrome://openpgp/skin/enigEncForceNo.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[encrypted="activeNone"] #enigmail-encrypted-status {
+ list-style-image: url("chrome://openpgp/skin/enigEncActiveNone.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[encrypted="activePlus"] #enigmail-encrypted-status {
+ list-style-image: url("chrome://openpgp/skin/enigEncActivePlus.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[encrypted="activeMinus"] #enigmail-encrypted-status {
+ list-style-image: url("chrome://openpgp/skin/enigEncActiveMinus.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[encrypted="activeConflict"] #enigmail-encrypted-status {
+ list-style-image: url("chrome://openpgp/skin/enigEncActiveConflict.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[encrypted="inactiveNone"] #enigmail-encrypted-status {
+ list-style-image: url("chrome://openpgp/skin/enigEncInactiveNone.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[encrypted="inactivePlus"] #enigmail-encrypted-status {
+ list-style-image: url("chrome://openpgp/skin/enigEncInactivePlus.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[encrypted="inactiveMinus"] #enigmail-encrypted-status {
+ list-style-image: url("chrome://openpgp/skin/enigEncInactiveMinus.png");
+ visibility: visible;
+}
+
+#enigmail-status-bar[encrypted="inactiveConflict"] #enigmail-encrypted-status {
+ list-style-image: url("chrome://openpgp/skin/enigEncInactiveConflict.png");
+ visibility: visible;
+}
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-win/headerProtected-18.svg
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ viewBox="0 0 56.514998 56.514999"
+ version="1.1"
+ id="svg8"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06"
+ sodipodi:docname="headerProtected-18.svg"
+ inkscape:export-xdpi="2.27"
+ inkscape:export-ydpi="2.27">
+ <defs
+ id="defs2">
+ <linearGradient
+ id="linearGradient8317"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#ff0000;stop-opacity:1;"
+ offset="0"
+ id="stop8315" />
+ </linearGradient>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="18"
+ inkscape:cx="16.413862"
+ inkscape:cy="10.472223"
+ inkscape:document-units="mm"
+ inkscape:current-layer="layer3"
+ showgrid="false"
+ inkscape:snap-to-guides="true"
+ inkscape:snap-grids="true"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1239"
+ inkscape:window-height="755"
+ inkscape:window-x="41"
+ inkscape:window-y="1"
+ inkscape:window-maximized="1"
+ fit-margin-top="0"
+ fit-margin-left="-0.8"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ units="px"
+ scale-x="7.4">
+ <sodipodi:guide
+ position="3883.8708,741.49345"
+ orientation="0,1"
+ id="guide4697"
+ inkscape:locked="false" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata5">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="Layer 2"
+ style="display:inline"
+ transform="translate(-46.896674,-179.42294)">
+ <g
+ id="g6697"
+ transform="matrix(1,0,0,0.95416667,0,10.813822)">
+ <image
+ width="29.903757"
+ height="10.697155"
+ preserveAspectRatio="none"
+ style="image-rendering:optimizeQuality"
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZ0AAAAlCAYAAACZMTQOAAAABHNCSVQICAgIfAhkiAAAANhJREFU eJzt1bFtgwAAAMEYUSQFFBEzMg5LsEVGYBZE78qZIo8U303w3T+WZXmd5/kBAH9pXdefwXAAKFzX 9TXcHQHA+zAdADKmA0DGdADImA4AGdMBIGM6AGRMB4CM6QCQMR0AMqYDQMZ0AMiYDgAZ0wEgYzoA ZEwHgIzpAJAxHQAypgNAxnQAyJgOABnTASBjOgBkTAeAjOkAkDEdADKmA0DGdADImA4AGdMBIGM6 AGRMB4CM6QCQGeZ5vrsBgDcwTdPzsW3b93Ecn3fHAPB/jeP42vf9/AWoNBUXx/LG4QAAAABJRU5E rkJggg== "
+ id="image4638"
+ x="60.220459"
+ y="185.83905" />
+ <path
+ sodipodi:nodetypes="cccccccccccccccccccc"
+ inkscape:connector-curvature="0"
+ id="path4569"
+ d="m 53.961054,182.24537 c 0,-1.18467 -0.02564,-3.06908 3.689545,-3.06908 7.699536,0 24.843888,-3.1e-4 35.936576,0 2.673147,0 2.673147,3.3698 2.673147,3.3698 0,0 0.08347,9.53394 0.08488,21.8929 l 0.0021,20.50537 -4.089856,4.26283 -4.089848,4.26285 -31.646457,-0.0175 c -2.559641,0 -2.559641,-3.32743 -2.559641,-3.32743 V 182.2453 m 31.760891,40.60948 3.825229,-0.015 4.234526,0.015 v -40.03579 l -36.85476,-0.002 v 47.70131 c 10.424891,-0.0325 16.911425,-0.0246 28.795002,-0.0246 v -7.63872 z"
+ style="fill:#000000;stroke-width:0.42511523" />
+ <image
+ width="29.903757"
+ height="2.6499548"
+ preserveAspectRatio="none"
+ style="image-rendering:optimizeQuality"
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZ0AAAAlCAYAAACZMTQOAAAABHNCSVQICAgIfAhkiAAAANhJREFU eJzt1bFtgwAAAMEYUSQFFBEzMg5LsEVGYBZE78qZIo8U303w3T+WZXmd5/kBAH9pXdefwXAAKFzX 9TXcHQHA+zAdADKmA0DGdADImA4AGdMBIGM6AGRMB4CM6QCQMR0AMqYDQMZ0AMiYDgAZ0wEgYzoA ZEwHgIzpAJAxHQAypgNAxnQAyJgOABnTASBjOgBkTAeAjOkAkDEdADKmA0DGdADImA4AGdMBIGM6 AGRMB4CM6QCQGeZ5vrsBgDcwTdPzsW3b93Ecn3fHAPB/jeP42vf9/AWoNBUXx/LG4QAAAABJRU5E rkJggg== "
+ id="image4649"
+ x="60.220459"
+ y="203.46257" />
+ <image
+ width="29.903757"
+ height="2.6499548"
+ preserveAspectRatio="none"
+ style="image-rendering:optimizeQuality"
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZ0AAAAlCAYAAACZMTQOAAAABHNCSVQICAgIfAhkiAAAANhJREFU eJzt1bFtgwAAAMEYUSQFFBEzMg5LsEVGYBZE78qZIo8U303w3T+WZXmd5/kBAH9pXdefwXAAKFzX 9TXcHQHA+zAdADKmA0DGdADImA4AGdMBIGM6AGRMB4CM6QCQMR0AMqYDQMZ0AMiYDgAZ0wEgYzoA ZEwHgIzpAJAxHQAypgNAxnQAyJgOABnTASBjOgBkTAeAjOkAkDEdADKmA0DGdADImA4AGdMBIGM6 AGRMB4CM6QCQGeZ5vrsBgDcwTdPzsW3b93Ecn3fHAPB/jeP42vf9/AWoNBUXx/LG4QAAAABJRU5E rkJggg== "
+ id="image4671"
+ x="60.220459"
+ y="211.76212" />
+ <image
+ width="23.725101"
+ height="2.6499548"
+ preserveAspectRatio="none"
+ style="image-rendering:optimizeQuality"
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZ0AAAAlCAYAAACZMTQOAAAABHNCSVQICAgIfAhkiAAAANhJREFU eJzt1bFtgwAAAMEYUSQFFBEzMg5LsEVGYBZE78qZIo8U303w3T+WZXmd5/kBAH9pXdefwXAAKFzX 9TXcHQHA+zAdADKmA0DGdADImA4AGdMBIGM6AGRMB4CM6QCQMR0AMqYDQMZ0AMiYDgAZ0wEgYzoA ZEwHgIzpAJAxHQAypgNAxnQAyJgOABnTASBjOgBkTAeAjOkAkDEdADKmA0DGdADImA4AGdMBIGM6 AGRMB4CM6QCQGeZ5vrsBgDcwTdPzsW3b93Ecn3fHAPB/jeP42vf9/AWoNBUXx/LG4QAAAABJRU5E rkJggg== "
+ id="image4682"
+ x="60.220459"
+ y="220.06163" />
+ <rect
+ style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.87403107;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect6711"
+ width="2.3547916"
+ height="4.9358077"
+ x="63.249397"
+ y="188.49934" />
+ <rect
+ y="188.49934"
+ x="68.613091"
+ height="4.9358077"
+ width="2.3547916"
+ id="rect6766"
+ style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.87403107;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <rect
+ style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.87403107;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect6768"
+ width="2.3547916"
+ height="4.9358077"
+ x="74.238426"
+ y="188.49934" />
+ <rect
+ y="188.49934"
+ x="79.340477"
+ height="4.9358077"
+ width="2.3547916"
+ id="rect6772"
+ style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.87403107;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <rect
+ style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.87403107;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect6774"
+ width="2.3547916"
+ height="4.9358077"
+ x="84.965813"
+ y="188.49934" />
+ </g>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer3"
+ inkscape:label="Layer 1"
+ transform="translate(0,-4.4914085e-6)"
+ style="display:inline">
+ <rect
+ style="opacity:1;fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.48279953;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect6778"
+ width="50.889664"
+ height="12.689711"
+ x="3.0089004"
+ y="7.8493094" />
+ </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-win/headerUnprotected-18.svg
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ viewBox="0 0 56.514998 56.514999"
+ version="1.1"
+ id="svg8"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06"
+ sodipodi:docname="headerUnprotected-18.svg"
+ inkscape:export-xdpi="2.27"
+ inkscape:export-ydpi="2.27">
+ <defs
+ id="defs2">
+ <linearGradient
+ id="linearGradient8317"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#ff0000;stop-opacity:1;"
+ offset="0"
+ id="stop8315" />
+ </linearGradient>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="18"
+ inkscape:cx="16.413862"
+ inkscape:cy="10.472223"
+ inkscape:document-units="mm"
+ inkscape:current-layer="layer3"
+ showgrid="false"
+ inkscape:snap-to-guides="true"
+ inkscape:snap-grids="true"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1239"
+ inkscape:window-height="755"
+ inkscape:window-x="41"
+ inkscape:window-y="1"
+ inkscape:window-maximized="1"
+ fit-margin-top="0"
+ fit-margin-left="-0.8"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ units="px"
+ scale-x="7.4">
+ <sodipodi:guide
+ position="3883.8708,741.49345"
+ orientation="0,1"
+ id="guide4697"
+ inkscape:locked="false" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata5">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="Layer 2"
+ style="display:inline"
+ transform="translate(-46.896674,-179.42294)">
+ <g
+ id="g6697"
+ transform="matrix(1,0,0,0.95416667,0,10.813822)">
+ <image
+ width="29.903757"
+ height="10.697155"
+ preserveAspectRatio="none"
+ style="image-rendering:optimizeQuality"
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZ0AAAAlCAYAAACZMTQOAAAABHNCSVQICAgIfAhkiAAAANhJREFU eJzt1bFtgwAAAMEYUSQFFBEzMg5LsEVGYBZE78qZIo8U303w3T+WZXmd5/kBAH9pXdefwXAAKFzX 9TXcHQHA+zAdADKmA0DGdADImA4AGdMBIGM6AGRMB4CM6QCQMR0AMqYDQMZ0AMiYDgAZ0wEgYzoA ZEwHgIzpAJAxHQAypgNAxnQAyJgOABnTASBjOgBkTAeAjOkAkDEdADKmA0DGdADImA4AGdMBIGM6 AGRMB4CM6QCQGeZ5vrsBgDcwTdPzsW3b93Ecn3fHAPB/jeP42vf9/AWoNBUXx/LG4QAAAABJRU5E rkJggg== "
+ id="image4638"
+ x="60.220459"
+ y="185.83905" />
+ <path
+ sodipodi:nodetypes="cccccccccccccccccccc"
+ inkscape:connector-curvature="0"
+ id="path4569"
+ d="m 53.961054,182.24537 c 0,-1.18467 -0.02564,-3.06908 3.689545,-3.06908 7.699536,0 24.843888,-3.1e-4 35.936576,0 2.673147,0 2.673147,3.3698 2.673147,3.3698 0,0 0.08347,9.53394 0.08488,21.8929 l 0.0021,20.50537 -4.089856,4.26283 -4.089848,4.26285 -31.646457,-0.0175 c -2.559641,0 -2.559641,-3.32743 -2.559641,-3.32743 V 182.2453 m 31.760891,40.60948 3.825229,-0.015 4.234526,0.015 v -40.03579 l -36.85476,-0.002 v 47.70131 c 10.424891,-0.0325 16.911425,-0.0246 28.795002,-0.0246 v -7.63872 z"
+ style="fill:#000000;stroke-width:0.42511523" />
+ <image
+ width="29.903757"
+ height="2.6499548"
+ preserveAspectRatio="none"
+ style="image-rendering:optimizeQuality"
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZ0AAAAlCAYAAACZMTQOAAAABHNCSVQICAgIfAhkiAAAANhJREFU eJzt1bFtgwAAAMEYUSQFFBEzMg5LsEVGYBZE78qZIo8U303w3T+WZXmd5/kBAH9pXdefwXAAKFzX 9TXcHQHA+zAdADKmA0DGdADImA4AGdMBIGM6AGRMB4CM6QCQMR0AMqYDQMZ0AMiYDgAZ0wEgYzoA ZEwHgIzpAJAxHQAypgNAxnQAyJgOABnTASBjOgBkTAeAjOkAkDEdADKmA0DGdADImA4AGdMBIGM6 AGRMB4CM6QCQGeZ5vrsBgDcwTdPzsW3b93Ecn3fHAPB/jeP42vf9/AWoNBUXx/LG4QAAAABJRU5E rkJggg== "
+ id="image4649"
+ x="60.220459"
+ y="203.46257" />
+ <image
+ width="29.903757"
+ height="2.6499548"
+ preserveAspectRatio="none"
+ style="image-rendering:optimizeQuality"
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZ0AAAAlCAYAAACZMTQOAAAABHNCSVQICAgIfAhkiAAAANhJREFU eJzt1bFtgwAAAMEYUSQFFBEzMg5LsEVGYBZE78qZIo8U303w3T+WZXmd5/kBAH9pXdefwXAAKFzX 9TXcHQHA+zAdADKmA0DGdADImA4AGdMBIGM6AGRMB4CM6QCQMR0AMqYDQMZ0AMiYDgAZ0wEgYzoA ZEwHgIzpAJAxHQAypgNAxnQAyJgOABnTASBjOgBkTAeAjOkAkDEdADKmA0DGdADImA4AGdMBIGM6 AGRMB4CM6QCQGeZ5vrsBgDcwTdPzsW3b93Ecn3fHAPB/jeP42vf9/AWoNBUXx/LG4QAAAABJRU5E rkJggg== "
+ id="image4671"
+ x="60.220459"
+ y="211.76212" />
+ <image
+ width="23.725101"
+ height="2.6499548"
+ preserveAspectRatio="none"
+ style="image-rendering:optimizeQuality"
+ xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZ0AAAAlCAYAAACZMTQOAAAABHNCSVQICAgIfAhkiAAAANhJREFU eJzt1bFtgwAAAMEYUSQFFBEzMg5LsEVGYBZE78qZIo8U303w3T+WZXmd5/kBAH9pXdefwXAAKFzX 9TXcHQHA+zAdADKmA0DGdADImA4AGdMBIGM6AGRMB4CM6QCQMR0AMqYDQMZ0AMiYDgAZ0wEgYzoA ZEwHgIzpAJAxHQAypgNAxnQAyJgOABnTASBjOgBkTAeAjOkAkDEdADKmA0DGdADImA4AGdMBIGM6 AGRMB4CM6QCQGeZ5vrsBgDcwTdPzsW3b93Ecn3fHAPB/jeP42vf9/AWoNBUXx/LG4QAAAABJRU5E rkJggg== "
+ id="image4682"
+ x="60.220459"
+ y="220.06163" />
+ <rect
+ style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.87403107;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect6711"
+ width="2.3547916"
+ height="4.9358077"
+ x="63.249397"
+ y="188.49934" />
+ <rect
+ y="188.49934"
+ x="68.613091"
+ height="4.9358077"
+ width="2.3547916"
+ id="rect6766"
+ style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.87403107;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <rect
+ style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.87403107;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect6768"
+ width="2.3547916"
+ height="4.9358077"
+ x="74.238426"
+ y="188.49934" />
+ <rect
+ y="188.49934"
+ x="79.340477"
+ height="4.9358077"
+ width="2.3547916"
+ id="rect6772"
+ style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.87403107;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <rect
+ style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.87403107;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect6774"
+ width="2.3547916"
+ height="4.9358077"
+ x="84.965813"
+ y="188.49934" />
+ </g>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer3"
+ inkscape:label="Layer 1"
+ transform="translate(0,-4.4914085e-6)"
+ style="display:none">
+ <rect
+ style="opacity:1;fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:16.48279953;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect6778"
+ width="50.889664"
+ height="12.689711"
+ x="3.0089004"
+ y="7.8493094" />
+ </g>
+</svg>
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..bdf46e6f46c92af3d5728711498456b1d7e8a2d4
GIT binary patch
literal 6834
zc$@*U8cpSiP)<h;3K|Lk000e1NJLTq003M7003eL1^@s6-Ji6D00006VoOIv00000
z008+zyMF)x010qNS#tmY79{`x79{~mQY7#I000McNliru;Rp&5EC=+k{%8OI8b3)y
zK~#9!?VV|m9oKc=fA@C3e)}w#eFht`k^m`gBB+RzsD&aeQX*r@vf_$k<wK-cskl`6
z7Q2#4<wGh_RH=%U$YrZsjwL&e%9SdK6iJa3DN><C2?7C<Al4ZyBruqLdGq$y-S?h+
zcyIRi27|!>7|6U;T`0`->v{d}bN}a@|2^lnI)DB=T5AB5Qp}E(ezw~GH~QJ-##%me
zvy9)|JNp3e55@<8-+<w+<wKRQLOHDbv-8>azd2c%?mC@0$6#)lye(1)O9-;yiRzo$
z3>>9N=s4S&`e=!^9Nyo$>-Tz6-2<J8cGA%l3bpbDL?8$t5&@-F54aVJh)@hlf0XkJ
zJEr{%{WE9mcxi%TQ>QsQ^FDb~Bp(#93jn{_=bft@Qcj#Hf1I}1CUG&}DVy?$5&OSF
zF)u|OkGLB{E4?!4wV_rUr4;<)zs?2uC!fFY^8H80kEf4j`WY#np&}Km4zYw-ZLnB$
z?J}a)3b@>Oqy?mQ0}74CMT^E2mraqL_d4TS4nMNx!QmZE+kX9-EuZ+CbToyl-K(dR
zit$@UOXGj>%i%Zg8Y&L=9G*Csyyy=zVaF&q87vkeAWj4#4p9|FX$N%`p`C05zyUD^
zBL-uAj0rGe2z<?i9_}rMg|`1U7UWN5JGYLM#_xNe`@qGnM90&;>7IVQ0?n)lcxXfN
zLH--3Ci@Tk==~pW@6VrSq;eJ;2~mzgCpC^%=tu<Zr63X^={6H_Tq4T73cxo6+*C|J
zU<1lw9_!B_#t?b|zR7tNoArJ<HJps8)F<DaJddNC)B9R?f2uvc>9d~WHKdq^lwy@|
zWcZywKb9T%{I^bjx3?(o6RHYfBtRUC<3{Li-9>Y36Z@KW(;n*}=Eg}mNfa72`v=$J
zGdDouBM68kl#rq+F<u%cA7(k4ImJX_keT8z$_K2%F&-(kmF{RC|Ni!WmD$&}_t^HP
ztsh&pg;$K<lMf3|9nbdfJ((Too$`kuQiO<wh(|i=5sNj`+q9Kdua)jZH%*ad;*mHp
z6+>wSv(xG(1-$C(ELIG08^_U#e3)d~6p1<t&w+`;G=zrG_sIlfNX7E@^of>uB(b?Q
z+Coz_y)25U3;0NJ?Ee1D*{}WS$?tBR3Wvd)Ld67eBalwBz2$c9Y~9b^)DEJKht`_V
z8kANTf||3Gn;vi_3WXx-c=Sbk0oc>Dli~6h7fVAtGyD>T@)YE!Q7X%f$??q-f5DfE
zBikd&J#=69UH3(uXk)+yA@3LZ4vrnU<Jk)@CbQ~7nrNX4b}B_8*1{*dKSo!)i;n0f
zD$2)~0I?PkP@+-D!YF7>0ixbOap9~3RCe|uXyRUsTih)?p&w?rG{S46zeMuWs31g`
z<nYuRRQ%$H`%>M<JL4VulU||$;3CXavb%?hLyw%vpKA$K0U{Rb3Q;djJhqAML@%vg
z6A|qZN&q5QRfAQWIuYKLLvA->0xZ=ySBt_`4lyT6Pof(GBx22!DtW|)DA#AqjMAS!
z!$f(Cgq!#eNiXpq>jGXd#q<joU)k~Nsb6g#HYZzb!s0~1rd&SKaTh(QJ`xd+G9?PO
zh@(~2f~B!J>$L>qR<U7+QXrrNBHE!lzKPHGe3Do7j~OeDBH4gkWt{V2f#)y2v~PdQ
zZNK|FyPjAMxNm%NmHY$8GbjJ?NM*zm*MbWY_2@|Sk&HIsIWZ~}F}7;e+PF2}XjcGS
zug#h^>JU}U90h#kAj*=A#A%QB0GjdgAYu(cQ0Cp4<7n+}DTifdL&Z73OF@a5O1A4*
z?$jM4X1v#WfCn)zN^f!-$w(aSSaQ~{g-ldqO<8|F-pF{*JJjkI)o47&r9Iw-(hifk
zA&fN`YdDb`AnvA;dB5<T=9v4{MSu@v&VS{6@%*C~O9K=mdGLUk#*4;jk8PqTW&F@b
zX1&Mk^jb{5>k@HXx5P)um09+JplkkC6<QpUkrXJ4iaS7nSYbSSp7#qeeR$%X-#gHC
z&sSTcE%Sh9f|<{ZS0)|_wI9VX-~bU9<$5S31QK9GEm8tkCyQ)MN)f=;9C{-qLFOP;
z=sDwBihx3iLX@p0j*%$%7My@kRVY$mxHPIN)}Q0>G?44hp6xi6J(adz2$9-vybi)h
z6CpljF<84KjMq0O;1*Mb&96GZs$-Wr=T*Z(tsI<)2CWDQ=99VwUpFF}K!u3c2Eil*
z0V<}*syv4#-by~ub3fhjPF$cB$W8f^+sDe|)oG>yt#Gu$(Uzi=s$Wr721c(FKZsmQ
zs4Y<&C2dmN5$+{M6h}FAEVoJ{N@*O&;e<NPG#N5d0tC(C5ZD(*AO=?}tk#GYL_;7x
zL!}{nGjHK8z(ggpeY8A@iAa^3wZc^z5DK=0!7f8$DKO4P2D}zc74hjd?X;K_9}N!B
zqML|BT%-=*3I!U((G1B5Q<7oE=5SEBOYYV|6N)9G1*3(qX0&BfX0SLy#a0%Fg+(cm
zrC3d=bk)jzd@PDdo2@M>*d<>`mRPv6VIvL;gks54;zQidqmfVX(2fU5c}bMpIPRYc
zr#V<S%#n#xobt~T7ll^KV=slmh)^Ivl&P_V5Q-ra<_R&20T&DwQ?u?_iwLlys*4Pm
z<rcln6rFY9W4Q(<ZfiFj594%r?QDtlk%}dWN8$~pCRC(CNy_wxXPGh6luQ;!$7|u>
z^6=M+Al9!w-UZLlC^H9xyJ+TXJHJY!1en2y!?X!%0GlPiC@#n1a>D`F)k94Y6^D<6
zpXMV?ck_?h4iNKV4IRHAjQf+kTRh3P$N!44@B-FW$V%8C2G$@JWCpO+gj6fUE(qEe
zr}i=rv=&exHUwk9nXa9(<c~;X73f(u1c`@+q-y5L_+#9eyq#n^S(B0L4_|zKka>oq
z#djI4oFTvm4B)S*E{e5)MU<*yxd7}s@@#_}wWjWGg<y3;{stIPiPNlF*cIDGN3;#c
zbs9e#2`Shj{iOlUmCxauVwEBkuw<5Bshupba9ho&Z0*ycz{=e0QFW*5YTVTtR$JD2
zJYXMUpSzEbc7BBBL^CT@Hw;!TalCYbA5Q&WM*WcmYT+t{Hw#LnO1YvIVc^yWNYyud
zS%X7di(0tecw6DF#2wrdyO(4<xuSLZHlV*ez$@8T$%Zq8HdroUtWDc(1o)CUax*AN
zmyt`N#^a8}e(p`)h39z<x!WQXZIM%@)4V$KDkW2@I`At1T*=B#-(76kH#xdXhY`9}
z4^MheaevDJcBXf%D5e!{k!Po$=e5jhoX8y`&;jW6(B|zbNLbWOkH1Td7QKnvV*6<F
znu)lP6{C`@$?<OKT?T_ed?{ZYiAa4fI%nakM)u38M=gjL<;wY4*7|L)ezclac6d8^
zs^dx8lkHW>xnV{*#reuP{%qpg<oztDD^qV*{O${IT0kjgjelX|4G|$KF@7iU6nCcX
zq+?SDp1X3+@KWX#-YL98Sgur;(A0V^^EHf8?DdfBuKCd75GTeSZ#O%l+lhK!L-Mp(
zeDOI~KFdI<A7jG#n0pNmS}mK^rcsnq&5+bw5u+>v-!5g8rCDU186r1;Qo8A47d!b(
z`@?KbY;LHP0OU-LxARANZt_PQExbdZ%=*X+HH_8e<M0h8^Id5eXcV_Zw((HxA=*-H
z#G~<s=LQlm>W}jL^z)4Rqxd$wx>D(e)eE*!rg`&oxE6<9k=;C$c!=hBGhW1Nn9UYV
ziA(+vFJzu&DwwR!?rY;!8i`kS`8jrh7J0c$vrg2a)=+x9HX2eSi1BIXVIFRJnESi#
zC+5Z)a&aI5FHFC{k^B)(X8Q@WU%k((L0r^46)mFWMsc{*w3M|dd>bI6bcwJhx`&=<
z4+$^PFw&@sQw5Hdk26p?Ln!{W%)S;Sl`g*_L#&W;QasiA6uX*s;lvwz%ACz{!N15~
zPy8i=<>A_cxjuC2F1Bu|6=l|@hAgIbwE~66YGe3okSr-He8T+{`=Yn8yKOfuiIx@5
zKbk+vOPQC*l?teEcJ`}vnPnn1#c&QkwTL6v#ljtNFk<TR<uZ0s>rW+!W{0zbz207$
zqD>7`ctNO0h2dbB(}mLn##F&j*SeOmE~OZ*RJ#@}#H>eK*h(eH;}a|cbauJbVli6C
zdwx}|Q|mQF9a>29Xxk$kNZn5)9%*=OUwn>a5A(|O%RD{x48{i6iSSm6!)Jx_dNfcc
zd}wuB`0eCVM2&|Qy)0pHwMLY}T1!z1yzalj5F<>;#L8B?%5bJdwem^tA-1Qsvnkcu
zu(u|ZkZhRcm6?}0T{w*~^E&ssDH?UOCS_$2Engj_2(-FwJlgVEw#hb9I#s2Xg%++g
zN+~1|ro(B<lRk&+Ato6^l)kE)b41gwJ9#AjTWm>gA)QP&JiTN~Ooo%ZoOy|X^0|3{
z)b%ZmcCUszwGk$QG5)&pSNz-FKVe65$FgeKIsI9nBkbTSv42T)(&N2cKRKteHqdKe
z?^U;QZ|oi(>wSz(iA^h}@ULcG<yheuZ_gZ~V2ibzxc+tMSM4;*5;7W&l9L=|RVM8$
zZX{YH_-knQ+S%l_(W2TY$|wf2CeTG`+EqL4PCIEYT`O0s?<kEJMuQQC{7aN=1xt8C
zz;3|dOES#`Li$3kvr>gMAIxxY@=czJ{)mf}i+rK;3wWx2I_)|xTf4UM7>|?o)BKm>
ze`QLh(Mz25<uXkX@bLIj^FLu<(>_wosfHRz#a0-pjPSjQzva!`LHt^hqpm^REwa$x
zRS&zG`pZ>m7B%!O)dt$17hdPZ%!^zqU!q_NjX&pV`jVUZ@TLRo*ZbMV)+_c&9dz;`
z=XUnD>}Pv=`-;Up)8RC)&Ai5Vag1`szfp#L1(dSD!`da$%Um?0;7ImO1}m5NgU&x7
zq9Y`oWCQYtu6P%T)&zHr--%~EF3N?)Hok(1i-lg@!`;r^>~7jkN4%rqxuJwi1{1t8
z{W8;)44C=8uWKgvi>#Pz=mkObf<mDMp^Tb8w321VY?f(4nDMiGYw*AG^O@J@szvKQ
z<)}+ja}!_g{O5eJ^$RowF(Ts3T~jIQ9<x*G?)csO(~jS#HQkCEa~miahIevr^Lpmz
z{BYuFrp)-9Q*Ctpt34L*?Gh?hS3oJUkdM@|W2pjnp)|;a(glVq!-R8nc+0I^YfWde
zlg)`O?1|h;j2L38*OXd{y!3F<Y}Q-Y9NSD!qNlohgQ`y@fmG{D&X&)T5A%d#=Vof7
z>tF4$8d+l9x@nZ72$ave`D45`^)p_We1YO@_*L(GrRZz#<DQOt`C9txY}Q*=sv-je
z3ERXMqhH_?&7WXr$4*?QzI6vSU?#}&cVmCcvy;!v*EVf{ZbWxkMp-vXssHrUvW!#^
zCA?X9i!n3C9Zh$zGroh~XkP;n23is=d}_-ldGq|Q*<@NdWDnsJE)I!A(L$QdPA{MD
ze1bjcJ=M0D24kQ!z@gkBj%80W=8s&x&V8k84a|#HOEFsaelevq88gY5@;NS*E-~tl
ztynAQX^+lCC)>U4+~VAVZm{h~t7@go>0nD@3(e8SwSl38j07W`DxPAxlA#oq*+^sk
z^>9@!0u914e>wae?rXW9+mg58X|Hb2rg9Ws%HxU7C)pR@&(8*b!nh78>u~N`7~!+&
z$GInQH=Elw6N|(ef<06j;>GC~__N{vK|%ZtK_d+;ybhgQ#V{Do<<E0`<~WD5hslTe
z70>TTb+Du57CsgG3_IPOa~VUF2wT)<?n>RshnnxKO0Ny(W!aYbS>|W-=LabGl^d46
zFT-y^pObr8RZ%TmfKV%EgbRLefeYn992Magnb+xy_K|dxI1L1k=2SDT=kiF?XDJlQ
zoc7NV>VPN-b~-!Q-?X1wn{LI?^*?XKkTW@6o%$(f@@F7W^LHoehJdZ={9U*1f`K$j
ze3tr1yRtl~6&J%nzIo}t(Hq~)0Xe{~#IE}7;10y2aUR|LTNKBN%=j68?!Q5w*TuhR
z`zkwIc9Ke_8dAiG!g2bG{X94IJkx##oEud6D-BA$SLHP2>iImd6{h`Z&KA!yP#PeR
zprI4hN|TDF=!^BSH@cT?Y8zXf?c9>Qg?J><u*}$Cc(42({iS|#L6*S!8-lN8rDD!&
z`ow+3)1yD&Wd1k@(g$dbwBpu_MifMn5$+D}q1|@UX}ai4baHR^y?9Ztu5}v>MN{PI
zi63xq=GO#aFn6tuIIhd8y*R?T!h7Vy63<OM%YDrU*q_|r@cd1wO(eVoZB3i-Tn{ha
zxQKo#nBr&Cukm)~UCtFQ5Gun)9@if5S^|?d1$1Sc{=xuTVq4jh*h56s&t5#&!*e_m
zPPMkA5!j)G<boV03MZKKCn$!68(a7xv%Niyr&xVGd@kdZMO>~f;}BL-;6w>I$@8tD
zKcz&89<PVp$=&rquchIQgsEy2WEt^CI8r#wHwV8#$*(MmsBRSC^B79q@OrV+v}*AO
z)_GX5l>7=Ob0_)9)KAEp{IWIBRmb^aIGFtvN3usKR7x1J8>_zEE^wSiR=J3L87<{X
zyV{f}#0UfV0X*&USjS^{%4@8huK;$@7I|a(7o5l)M*_g9wJh9F3s;vdb9NVMxN1(q
zlm_CJsz=XP6h<no7%E+2#?SJ@<{z>zwU5U;9>1dd8Y~ZTvT%Z@N1ox5e~GK#U3i_7
z9;+77%0is3|9*7}LJ26C0w)S5=r8qCFoo)-!K<37mhoVm{z5;~VTQ8tH*5%a87<{{
z&OA*(!1qSK$M;9R&zaI0N~W|50}{%n%%SW-{$}KR6vF)SMz5O<a3zgJtl1^FMLqj#
zIcBdHY6gpgeEa?X$@$WG{L5sBB~#+Xi5L0B<S#inGeAXx>LvnL^Ig{QP_ymwjVO0T
z3%?GAqr_4SioBb9moa~goXIsz`)3{HslrJHOM?`G4Q|~HzS|A04;Q7$n=Efly~T5*
z&vB`Ai7)kjsji|p9*lFYe2#Aq{VC(+>5Z|A{<>$H%fdmaBP}kp*tLNWisg-&H<&OJ
zJlOgm-SO^aq2k-ww>Vxn&U87CZ^Aj5d&61z5?Z-Dr7S8WzdpHyDB)1{O@2D{Dwisk
z$k!<(dOP<HzncCvVI>43h*DMl-f&d17(->HKHxE(An7#SkQEZG1qp?0d4}%}ewTxp
zU(Pwuyvg%c_6R?n_$jYVzDB5St}xKi9!*a3oI0w35FoDOB%Q?Sgpq6FKovn~IF&!e
z#nK?TFo(rbwq@QcooA>r#DqVNoh`f(+2Brdma{cUcfQ!vh3Y;^TNB&J`Z>;J->+`U
zVp-g)QZJLg+>+ZBnYmD*%)!Y+w0j+-bdt|^Kf!y&^L*>QZ*i$`v1%Ib2JcFr?Y^`M
zu|mR4u)k@)Bx*j%rP^ykU%F=~FWFuv1EHDcqU$YBSOQKJPw>p7!gV5yRE9ZII7iu*
zYAO5%k4|JkT5lntlI(5WQ%JapIl%J(y~)kPWvTQ!L_mVYjh!pkC$2NMX!^@%c;CK<
zr=#S8JcFf6bDLLgXv<#Gnxjx8-2{7D_6{f9_#EIxfZa{Izn^kb@xEBkzF|8`-sZt5
zloIB*30Id<3uPS)q$+1tN&(LfJy+Yj@+Q~{s<t7UGSz5VSw&~0oo&gj+}rWtucy59
zk|@QE{UGHfk9J18s_){l+&HqV?S_^&1&XH3%`|5FG_`2DVqJ8`x=BWoKVIlG0Z2p=
zXWLZ!%*VSvK4!<IWh^)9NvIHAeQI;b-}RuDn{A_<T6Gw<`mZwJ*a#1{e~jB(Za<Os
z()BaVxEufDPxn5Y&6vy=&lS&ZpO8t!7-m~yM9HG{MYW2}4c1YH*tjMwQtfEA)gU`X
zlI}=1pXqyKdRuba-!;rM9py}SC%RwnPIS`~>#DX3SWu>RScMW>U8wkA%v*+4$gFi+
zMaoIj6Yr)k(f3A2tm8j51YDs=d+8rN)cJ}3^OaqH_(F?LL#V55_y&}<h|$%Cs(<L9
z*TO!GLYZ3kl&vlyKG61n|3`biKG2uy`&`mXuB@df6m7A#Ke?^tw)a2R_4tVgTR(b2
z8RaVr5>`Q1vrqINBIr^B*C?=6u*FoVB(CB-(fv6-*732SJ<WUmKB}Xudly@Cq?xpr
z{=>(*9(&+u{^*kjvxmNBt?{g?K0zgdI0`Wulq{g`TJWgWVZW#il%Cg(x1|cQpiov3
z#iPS%KlR()PZjQH{m@UgrndgOdY5b{&$`Ox!yWhiVn^%FyLw~27rsCEzgx~0&h?J?
zlZaN}SY}H~loC+YZp&&(w^|==+(@7==v9^3kU3Puh|;Co^S^(~m$@~4>-fjJKmPH=
zLQQJ5fCEIGC@r)+yCbzz-=4nn%W0>%_eAL=8JnSuPdTX8U@J$@eJNGjC_=8n$~Ow!
zN|rqTTA7Dd4xVyp^V(?9&Fqcue)G1}tpm3tb{tDO$%b9ga0Tx~&IkEF&W5>tPhb4O
zhmT|q-}-j$EyjZ>LPC@aQhT$<y2R2OX*Fk-FK1f;Luhi-bVl2Fyz_~}yOX>5Y~N%5
zrpar1eKqeyo(DV#YA^nY)CJsFgH@Kv^3>l?hU5FkD&w5Wo!mEF$#AN0iuVieQM3he
zHeZV&ufoDN2;j;p92Fs{(`-v@r`2s^f75NUHL-bWb8^efmgLrFlTPwKw|Q+Nj&?5p
z%JoH*av9!qv_nKk=uP(g?xwIcvN^itOK}~0bi6Wtn?m)t*6GdJ99hXATxA8T-Z<7B
zQ4!L*nO%w9ba)-y-EwzfYkccB`jUO$Oh?mK{I0oGQ_8}h&91DAs=oauv>_IIKq+-e
zu#3v5wAL?QG2U9A+endM^)>6d`nEvrqEz)KY7IKangCzz{~P^m6@dT7hVj3-clH6`
gAB+zGzX9X_11S?Li!{K2tpET307*qoM6N<$f&qAHng9R*
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-win/ok-sign.svg
@@ -0,0 +1,147 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="14.996866"
+ height="17.165913"
+ version="1.1"
+ id="svg38326"
+ sodipodi:docname="ok-sign.svg"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06"
+ viewBox="0 0 34.992688 42.914782">
+ <metadata
+ id="metadata38332">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title>Highlight</dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs38330">
+ <linearGradient
+ osb:paint="solid"
+ id="linearGradient4972">
+ <stop
+ id="stop4974"
+ offset="0"
+ style="stop-color:#d5d211;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ osb:paint="solid"
+ id="linearGradient4964">
+ <stop
+ id="stop4966"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2780">
+ <stop
+ offset="0"
+ style="stop-color:#ffffff"
+ id="stop2782" />
+ <stop
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0"
+ id="stop2784" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2834">
+ <stop
+ offset="0"
+ style="stop-color:#e59a00"
+ id="stop2836" />
+ <stop
+ offset="1"
+ style="stop-color:#faff7d;stop-opacity:.18367"
+ id="stop2838" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3057">
+ <stop
+ offset="0"
+ style="stop-color:#282828"
+ id="stop3059" />
+ <stop
+ offset="1"
+ style="stop-color:#282828;stop-opacity:0"
+ id="stop3061" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2713">
+ <stop
+ offset="0"
+ style="stop-color:#000000"
+ id="stop2715" />
+ <stop
+ offset="1"
+ style="stop-color:#ffffff"
+ id="stop2717" />
+ </linearGradient>
+ </defs>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1199"
+ inkscape:window-height="731"
+ id="namedview38328"
+ showgrid="false"
+ fit-margin-top="0.2"
+ fit-margin-left="0.2"
+ fit-margin-right="0.2"
+ fit-margin-bottom="0.2"
+ inkscape:zoom="12.183333"
+ inkscape:cx="11.164775"
+ inkscape:cy="-1.8436479"
+ inkscape:window-x="46"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="layer6"
+ viewbox-width="18"
+ viewbox-height="22" />
+ <title
+ id="title38274">Highlight</title>
+ <g
+ inkscape:groupmode="layer"
+ id="layer5"
+ inkscape:label="Layer 1"
+ style="display:inline"
+ transform="translate(-9.0161178,10.157503)"
+ sodipodi:insensitive="true" />
+ <g
+ inkscape:groupmode="layer"
+ id="layer6"
+ inkscape:label="Layer 2"
+ style="display:inline"
+ transform="translate(-9.0161178,10.157503)">
+ <g
+ id="layer3"
+ inkscape:label="Layer 4"
+ transform="matrix(1.9578174,0,0,1.8768146,1.7731116,3.9800016)"
+ style="stroke-width:2.96904826;stroke-miterlimit:4;stroke-dasharray:none">
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#008000;stroke-width:2.96904826;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 5.1029077,6.1459338 c 0,0 4.0105782,2.9252466 5.7695983,5.6644272 4.278402,-11.00488643 9.869697,-17.5760024 9.869697,-17.5760024"
+ id="path4586"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccc" />
+ </g>
+ </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-win/password-error.svg
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="25.169001mm"
+ height="25.169033mm"
+ viewBox="0 0 25.169001 25.169033"
+ version="1.1"
+ id="svg8"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06"
+ sodipodi:docname="password-error.svg">
+ <defs
+ id="defs2" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="2.1594276"
+ inkscape:cx="139.34847"
+ inkscape:cy="68.416928"
+ inkscape:document-units="mm"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:window-width="1399"
+ inkscape:window-height="855"
+ inkscape:window-x="41"
+ inkscape:window-y="1"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata5">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-8.7120523,-4.2052231)">
+ <path
+ style="opacity:1;fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.83094561;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:14.39999962;stroke-dasharray:none;stroke-opacity:1"
+ id="path815"
+ sodipodi:type="arc"
+ sodipodi:cx="21.296553"
+ sodipodi:cy="16.78974"
+ sodipodi:rx="12.5845"
+ sodipodi:ry="12.584517"
+ sodipodi:start="0"
+ sodipodi:end="6.2656622"
+ sodipodi:open="true"
+ d="M 33.881053,16.78974 A 12.5845,12.584517 0 0 1 21.351682,29.374135 12.5845,12.584517 0 0 1 8.7125354,16.899998 12.5845,12.584517 0 0 1 21.131168,4.2063099 12.5845,12.584517 0 0 1 33.879121,16.569231" />
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.08675575;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 24.849867,9.3258801 3.897186,3.9041669 -3.775033,3.781809 3.51511,3.52141 -3.61439,3.620874 -3.515111,-3.52142 -3.614391,3.620879 -3.897185,-3.904175 3.614392,-3.62087 -3.591526,-3.597964 3.614398,-3.6208701 3.591517,3.5979611 z"
+ id="path817"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccccccccccccc" />
+ </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-win/sign-active-18.svg
@@ -0,0 +1,340 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ version="1.1"
+ id="svg38326"
+ sodipodi:docname="sign-active.svg"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06">
+ <metadata
+ id="metadata38332">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title>Highlight</dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs38330">
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top"
+ id="linearGradient38877"
+ x1="0"
+ y1="0"
+ x2="7.7620873"
+ y2="0"
+ gradientTransform="scale(0.64415662,1.5524175)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top"
+ id="linearGradient38879"
+ x1="-1.9364917"
+ y1="-11.61895"
+ x2="1.9364917"
+ y2="-11.61895"
+ gradientTransform="scale(1.2909944,0.77459667)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top_center"
+ id="linearGradient38881"
+ x1="-1.4142137"
+ y1="-12.020815"
+ x2="1.4142137"
+ y2="-12.020815"
+ gradientTransform="scale(1.4142136,0.70710678)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top"
+ id="linearGradient38883"
+ x1="-2.236068"
+ y1="5.0311527"
+ x2="2.236068"
+ y2="5.0311527"
+ gradientTransform="scale(1.118034,0.89442719)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top_center"
+ id="linearGradient38885"
+ x1="-1.8708287"
+ y1="4.2761798"
+ x2="1.8708287"
+ y2="4.2761798"
+ gradientTransform="scale(1.069045,0.93541435)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top"
+ id="linearGradient38887"
+ x1="-1.0473186"
+ y1="10.741717"
+ x2="1.0473186"
+ y2="10.741717"
+ gradientTransform="scale(2.3870482,0.41892744)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ osb:paint="solid"
+ id="linearGradient4972">
+ <stop
+ id="stop4974"
+ offset="0"
+ style="stop-color:#d5d211;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ osb:paint="solid"
+ id="linearGradient4964">
+ <stop
+ id="stop4966"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2780">
+ <stop
+ offset="0"
+ style="stop-color:#ffffff"
+ id="stop2782" />
+ <stop
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0"
+ id="stop2784" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2834">
+ <stop
+ offset="0"
+ style="stop-color:#e59a00"
+ id="stop2836" />
+ <stop
+ offset="1"
+ style="stop-color:#faff7d;stop-opacity:.18367"
+ id="stop2838" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3057">
+ <stop
+ offset="0"
+ style="stop-color:#282828"
+ id="stop3059" />
+ <stop
+ offset="1"
+ style="stop-color:#282828;stop-opacity:0"
+ id="stop3061" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2713">
+ <stop
+ offset="0"
+ style="stop-color:#000000"
+ id="stop2715" />
+ <stop
+ offset="1"
+ style="stop-color:#ffffff"
+ id="stop2717" />
+ </linearGradient>
+ </defs>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1199"
+ inkscape:window-height="731"
+ id="namedview38328"
+ showgrid="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:zoom="14.75"
+ inkscape:cx="6.2137977"
+ inkscape:cy="8.1121029"
+ inkscape:window-x="46"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="layer6" />
+ <title
+ id="title38274">Highlight</title>
+ <g
+ inkscape:groupmode="layer"
+ id="layer5"
+ inkscape:label="Layer 1"
+ style="display:inline"
+ sodipodi:insensitive="true">
+ <g
+ id="add"
+ transform="matrix(1.3457681,0,0,1.3457681,8.0886149,9.9113851)">
+ <defs
+ id="defs38291">
+ <linearGradient
+ id="top"
+ x1="0"
+ y1="0"
+ x2="1"
+ y2="0">
+ <stop
+ offset="0%"
+ style="stop-color:#e4ba2f;"
+ id="stop38276" />
+ <stop
+ offset="100%"
+ style="stop-color:#c36a00;"
+ id="stop38278" />
+ </linearGradient>
+ <linearGradient
+ id="top_center"
+ x1="0"
+ y1="0"
+ x2="1"
+ y2="0">
+ <stop
+ offset="0%"
+ style="stop-color:#f7e1c9;"
+ id="stop38281" />
+ <stop
+ offset="100%"
+ style="stop-color:#f4cfa5;"
+ id="stop38283" />
+ </linearGradient>
+ <linearGradient
+ id="hold"
+ x1="-3.3561783"
+ y1="-3.8734536"
+ x2="3.3561783"
+ y2="-3.8734536"
+ gradientTransform="scale(0.59591592,1.6780891)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0%"
+ style="stop-color:#f8edc3;"
+ id="stop38286" />
+ <stop
+ offset="100%"
+ style="stop-color:#f4d644;"
+ id="stop38288" />
+ </linearGradient>
+ </defs>
+ <g
+ transform="rotate(45)"
+ id="g38295">
+ <rect
+ width="5"
+ height="12.05"
+ transform="translate(-2.5,-7.5)"
+ style="fill:url(#linearGradient38877)"
+ id="rect38293"
+ x="0"
+ y="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38303">
+ <ellipse
+ cx="0"
+ cy="-7.5"
+ rx="2.5"
+ ry="1.5"
+ style="fill:url(#linearGradient38879)"
+ id="ellipse38297" />
+ <ellipse
+ cx="0"
+ cy="-7.5"
+ rx="2"
+ ry="1"
+ style="fill:url(#linearGradient38881)"
+ id="ellipse38299" />
+ <ellipse
+ cx="0"
+ cy="-7.5"
+ rx="0.75"
+ ry="0.34999999"
+ style="fill:#3d2c0c"
+ id="ellipse38301" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38307">
+ <path
+ d="m -2.5,4.5 2.5,4 2.5,-4 z"
+ style="fill:url(#linearGradient38883)"
+ id="path38305"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38311">
+ <path
+ d="M -2,4 0,7.5 2,4 Z"
+ style="fill:url(#linearGradient38885)"
+ id="path38309"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38315">
+ <path
+ d="m -2.5,4.5 a 4,4 0 0 0 5,0 z"
+ style="fill:url(#linearGradient38887)"
+ id="path38313"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38319">
+ <path
+ d="M -2,4 A 3,3 0 0 0 2,4 V -6.5 a 3.8,3.8 0 0 1 -4,0 z"
+ style="fill:url(#hold)"
+ id="path38317"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38323">
+ <path
+ d="M -1,7 0,8.5 1,7 a 2,2 0 0 1 -2,0 z"
+ style="fill:#3d2c0c"
+ id="path38321"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer6"
+ inkscape:label="Layer 2"
+ style="display:inline"
+ sodipodi:insensitive="true">
+ <g
+ id="layer3"
+ inkscape:label="Layer 4"
+ transform="matrix(1.0666667,0,0,1.0666667,-0.27745623,0.00654138)">
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#008000;stroke-width:1.74388254;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 11.90849,12.21419 c 0,0 1.39836,1.035106 2.568841,2.4924 0.454732,-3.871985 1.725652,-6.1303502 1.725652,-6.1303502"
+ id="path4586"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccc" />
+ </g>
+ </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-win/sign-disabled-18.svg
@@ -0,0 +1,368 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ version="1.1"
+ id="svg38326"
+ sodipodi:docname="sign-disabled.svg"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06">
+ <metadata
+ id="metadata38332">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title>Highlight</dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs38330">
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top"
+ id="linearGradient38877"
+ x1="0"
+ y1="0"
+ x2="7.7620873"
+ y2="0"
+ gradientTransform="scale(0.64415662,1.5524175)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top"
+ id="linearGradient38879"
+ x1="-1.9364917"
+ y1="-11.61895"
+ x2="1.9364917"
+ y2="-11.61895"
+ gradientTransform="scale(1.2909944,0.77459667)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top_center"
+ id="linearGradient38881"
+ x1="-1.4142137"
+ y1="-12.020815"
+ x2="1.4142137"
+ y2="-12.020815"
+ gradientTransform="scale(1.4142136,0.70710678)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top"
+ id="linearGradient38883"
+ x1="-2.236068"
+ y1="5.0311527"
+ x2="2.236068"
+ y2="5.0311527"
+ gradientTransform="scale(1.118034,0.89442719)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top_center"
+ id="linearGradient38885"
+ x1="-1.8708287"
+ y1="4.2761798"
+ x2="1.8708287"
+ y2="4.2761798"
+ gradientTransform="scale(1.069045,0.93541435)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top"
+ id="linearGradient38887"
+ x1="-1.0473186"
+ y1="10.741717"
+ x2="1.0473186"
+ y2="10.741717"
+ gradientTransform="scale(2.3870482,0.41892744)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ osb:paint="solid"
+ id="linearGradient4972">
+ <stop
+ id="stop4974"
+ offset="0"
+ style="stop-color:#d5d211;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ osb:paint="solid"
+ id="linearGradient4964">
+ <stop
+ id="stop4966"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2780">
+ <stop
+ offset="0"
+ style="stop-color:#ffffff"
+ id="stop2782" />
+ <stop
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0"
+ id="stop2784" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2834">
+ <stop
+ offset="0"
+ style="stop-color:#e59a00"
+ id="stop2836" />
+ <stop
+ offset="1"
+ style="stop-color:#faff7d;stop-opacity:.18367"
+ id="stop2838" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3057">
+ <stop
+ offset="0"
+ style="stop-color:#282828"
+ id="stop3059" />
+ <stop
+ offset="1"
+ style="stop-color:#282828;stop-opacity:0"
+ id="stop3061" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2713">
+ <stop
+ offset="0"
+ style="stop-color:#000000"
+ id="stop2715" />
+ <stop
+ offset="1"
+ style="stop-color:#ffffff"
+ id="stop2717" />
+ </linearGradient>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Greyscale"
+ id="filter40084">
+ <feColorMatrix
+ values="0.21 0.72 0.072 -0.3 0 0.21 0.72 0.072 -0.3 0 0.21 0.72 0.072 -0.3 0 0 0 0 1 0 "
+ id="feColorMatrix40082"
+ result="fbSourceGraphic" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix40328" />
+ <feColorMatrix
+ id="feColorMatrix40330"
+ values="0.21 0.72 0.072 0.3 0 0.21 0.72 0.072 0.3 0 0.21 0.72 0.072 0.3 0 0 0 0 1 0 "
+ in="fbSourceGraphic" />
+ </filter>
+ <filter
+ id="filter5183"
+ inkscape:label="Greyscale"
+ style="color-interpolation-filters:sRGB;">
+ <feColorMatrix
+ result="fbSourceGraphic"
+ id="feColorMatrix5181"
+ values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0" />
+ <feColorMatrix
+ id="feColorMatrix5185"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ in="fbSourceGraphic"
+ result="fbSourceGraphicAlpha" />
+ <feColorMatrix
+ result="fbSourceGraphic"
+ in="fbSourceGraphic"
+ values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"
+ id="feColorMatrix5187" />
+ <feColorMatrix
+ id="feColorMatrix5299"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ in="fbSourceGraphic"
+ result="fbSourceGraphicAlpha" />
+ <feColorMatrix
+ in="fbSourceGraphic"
+ values="0.21 0.72 0.072 -0.3 0 0.21 0.72 0.072 -0.3 0 0.21 0.72 0.072 -0.3 0 0 0 0 1 0 "
+ id="feColorMatrix5301" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1199"
+ inkscape:window-height="731"
+ id="namedview38328"
+ showgrid="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:zoom="14.75"
+ inkscape:cx="6.2137977"
+ inkscape:cy="8.1121029"
+ inkscape:window-x="46"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="layer5" />
+ <title
+ id="title38274">Highlight</title>
+ <g
+ inkscape:groupmode="layer"
+ id="layer5"
+ inkscape:label="Layer 1"
+ style="display:inline">
+ <g
+ id="add"
+ transform="matrix(1.3457681,0,0,1.3457681,8.0886149,9.9113851)"
+ style="filter:url(#filter40084)">
+ <defs
+ id="defs38291">
+ <linearGradient
+ id="top"
+ x1="0"
+ y1="0"
+ x2="1"
+ y2="0">
+ <stop
+ offset="0%"
+ style="stop-color:#e4ba2f;"
+ id="stop38276" />
+ <stop
+ offset="100%"
+ style="stop-color:#c36a00;"
+ id="stop38278" />
+ </linearGradient>
+ <linearGradient
+ id="top_center"
+ x1="0"
+ y1="0"
+ x2="1"
+ y2="0">
+ <stop
+ offset="0%"
+ style="stop-color:#f7e1c9;"
+ id="stop38281" />
+ <stop
+ offset="100%"
+ style="stop-color:#f4cfa5;"
+ id="stop38283" />
+ </linearGradient>
+ <linearGradient
+ id="hold"
+ x1="-3.3561783"
+ y1="-3.8734536"
+ x2="3.3561783"
+ y2="-3.8734536"
+ gradientTransform="scale(0.59591592,1.6780891)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0%"
+ style="stop-color:#f8edc3;"
+ id="stop38286" />
+ <stop
+ offset="100%"
+ style="stop-color:#f4d644;"
+ id="stop38288" />
+ </linearGradient>
+ </defs>
+ <g
+ transform="rotate(45)"
+ id="g38295">
+ <rect
+ width="5"
+ height="12.05"
+ transform="translate(-2.5,-7.5)"
+ style="fill:url(#linearGradient38877)"
+ id="rect38293"
+ x="0"
+ y="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38303">
+ <ellipse
+ cx="0"
+ cy="-7.5"
+ rx="2.5"
+ ry="1.5"
+ style="fill:url(#linearGradient38879)"
+ id="ellipse38297" />
+ <ellipse
+ cx="0"
+ cy="-7.5"
+ rx="2"
+ ry="1"
+ style="fill:url(#linearGradient38881)"
+ id="ellipse38299" />
+ <ellipse
+ cx="0"
+ cy="-7.5"
+ rx="0.75"
+ ry="0.34999999"
+ style="fill:#3d2c0c"
+ id="ellipse38301" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38307">
+ <path
+ d="m -2.5,4.5 2.5,4 2.5,-4 z"
+ style="fill:url(#linearGradient38883)"
+ id="path38305"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38311">
+ <path
+ d="M -2,4 0,7.5 2,4 Z"
+ style="fill:url(#linearGradient38885)"
+ id="path38309"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38315">
+ <path
+ d="m -2.5,4.5 a 4,4 0 0 0 5,0 z"
+ style="fill:url(#linearGradient38887)"
+ id="path38313"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38319">
+ <path
+ d="M -2,4 A 3,3 0 0 0 2,4 V -6.5 a 3.8,3.8 0 0 1 -4,0 z"
+ style="fill:url(#hold)"
+ id="path38317"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38323">
+ <path
+ d="M -1,7 0,8.5 1,7 a 2,2 0 0 1 -2,0 z"
+ style="fill:#3d2c0c"
+ id="path38321"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+ </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/skin/tb-win/sign-inactive-18.svg
@@ -0,0 +1,383 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ version="1.1"
+ id="svg38326"
+ sodipodi:docname="sign-inactive.svg"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06">
+ <metadata
+ id="metadata38332">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title>Highlight</dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs38330">
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top"
+ id="linearGradient38877"
+ x1="0"
+ y1="0"
+ x2="7.7620873"
+ y2="0"
+ gradientTransform="scale(0.64415662,1.5524175)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top"
+ id="linearGradient38879"
+ x1="-1.9364917"
+ y1="-11.61895"
+ x2="1.9364917"
+ y2="-11.61895"
+ gradientTransform="scale(1.2909944,0.77459667)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top_center"
+ id="linearGradient38881"
+ x1="-1.4142137"
+ y1="-12.020815"
+ x2="1.4142137"
+ y2="-12.020815"
+ gradientTransform="scale(1.4142136,0.70710678)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top"
+ id="linearGradient38883"
+ x1="-2.236068"
+ y1="5.0311527"
+ x2="2.236068"
+ y2="5.0311527"
+ gradientTransform="scale(1.118034,0.89442719)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top_center"
+ id="linearGradient38885"
+ x1="-1.8708287"
+ y1="4.2761798"
+ x2="1.8708287"
+ y2="4.2761798"
+ gradientTransform="scale(1.069045,0.93541435)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#top"
+ id="linearGradient38887"
+ x1="-1.0473186"
+ y1="10.741717"
+ x2="1.0473186"
+ y2="10.741717"
+ gradientTransform="scale(2.3870482,0.41892744)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ osb:paint="solid"
+ id="linearGradient4972">
+ <stop
+ id="stop4974"
+ offset="0"
+ style="stop-color:#d5d211;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ osb:paint="solid"
+ id="linearGradient4964">
+ <stop
+ id="stop4966"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2780">
+ <stop
+ offset="0"
+ style="stop-color:#ffffff"
+ id="stop2782" />
+ <stop
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0"
+ id="stop2784" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2834">
+ <stop
+ offset="0"
+ style="stop-color:#e59a00"
+ id="stop2836" />
+ <stop
+ offset="1"
+ style="stop-color:#faff7d;stop-opacity:.18367"
+ id="stop2838" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3057">
+ <stop
+ offset="0"
+ style="stop-color:#282828"
+ id="stop3059" />
+ <stop
+ offset="1"
+ style="stop-color:#282828;stop-opacity:0"
+ id="stop3061" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2713">
+ <stop
+ offset="0"
+ style="stop-color:#000000"
+ id="stop2715" />
+ <stop
+ offset="1"
+ style="stop-color:#ffffff"
+ id="stop2717" />
+ </linearGradient>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Greyscale"
+ id="filter40084">
+ <feColorMatrix
+ values="0.21 0.72 0.072 -0.3 0 0.21 0.72 0.072 -0.3 0 0.21 0.72 0.072 -0.3 0 0 0 0 1 0 "
+ id="feColorMatrix40082" />
+ </filter>
+ <filter
+ id="filter5183"
+ inkscape:label="Greyscale"
+ style="color-interpolation-filters:sRGB;">
+ <feColorMatrix
+ result="fbSourceGraphic"
+ id="feColorMatrix5181"
+ values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0" />
+ <feColorMatrix
+ id="feColorMatrix5185"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ in="fbSourceGraphic"
+ result="fbSourceGraphicAlpha" />
+ <feColorMatrix
+ result="fbSourceGraphic"
+ in="fbSourceGraphic"
+ values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"
+ id="feColorMatrix5187" />
+ <feColorMatrix
+ id="feColorMatrix5299"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ in="fbSourceGraphic"
+ result="fbSourceGraphicAlpha" />
+ <feColorMatrix
+ in="fbSourceGraphic"
+ values="0.21 0.72 0.072 -0.3 0 0.21 0.72 0.072 -0.3 0 0.21 0.72 0.072 -0.3 0 0 0 0 1 0 "
+ id="feColorMatrix5301" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1199"
+ inkscape:window-height="731"
+ id="namedview38328"
+ showgrid="false"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ inkscape:zoom="14.75"
+ inkscape:cx="6.2137977"
+ inkscape:cy="8.1121029"
+ inkscape:window-x="46"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="layer6" />
+ <title
+ id="title38274">Highlight</title>
+ <g
+ inkscape:groupmode="layer"
+ id="layer5"
+ inkscape:label="Layer 1"
+ style="display:inline"
+ sodipodi:insensitive="true">
+ <g
+ id="add"
+ transform="matrix(1.3457681,0,0,1.3457681,8.0886149,9.9113851)"
+ style="filter:url(#filter40084)">
+ <defs
+ id="defs38291">
+ <linearGradient
+ id="top"
+ x1="0"
+ y1="0"
+ x2="1"
+ y2="0">
+ <stop
+ offset="0%"
+ style="stop-color:#e4ba2f;"
+ id="stop38276" />
+ <stop
+ offset="100%"
+ style="stop-color:#c36a00;"
+ id="stop38278" />
+ </linearGradient>
+ <linearGradient
+ id="top_center"
+ x1="0"
+ y1="0"
+ x2="1"
+ y2="0">
+ <stop
+ offset="0%"
+ style="stop-color:#f7e1c9;"
+ id="stop38281" />
+ <stop
+ offset="100%"
+ style="stop-color:#f4cfa5;"
+ id="stop38283" />
+ </linearGradient>
+ <linearGradient
+ id="hold"
+ x1="-3.3561783"
+ y1="-3.8734536"
+ x2="3.3561783"
+ y2="-3.8734536"
+ gradientTransform="scale(0.59591592,1.6780891)"
+ gradientUnits="userSpaceOnUse">
+ <stop
+ offset="0%"
+ style="stop-color:#f8edc3;"
+ id="stop38286" />
+ <stop
+ offset="100%"
+ style="stop-color:#f4d644;"
+ id="stop38288" />
+ </linearGradient>
+ </defs>
+ <g
+ transform="rotate(45)"
+ id="g38295">
+ <rect
+ width="5"
+ height="12.05"
+ transform="translate(-2.5,-7.5)"
+ style="fill:url(#linearGradient38877)"
+ id="rect38293"
+ x="0"
+ y="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38303">
+ <ellipse
+ cx="0"
+ cy="-7.5"
+ rx="2.5"
+ ry="1.5"
+ style="fill:url(#linearGradient38879)"
+ id="ellipse38297" />
+ <ellipse
+ cx="0"
+ cy="-7.5"
+ rx="2"
+ ry="1"
+ style="fill:url(#linearGradient38881)"
+ id="ellipse38299" />
+ <ellipse
+ cx="0"
+ cy="-7.5"
+ rx="0.75"
+ ry="0.34999999"
+ style="fill:#3d2c0c"
+ id="ellipse38301" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38307">
+ <path
+ d="m -2.5,4.5 2.5,4 2.5,-4 z"
+ style="fill:url(#linearGradient38883)"
+ id="path38305"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38311">
+ <path
+ d="M -2,4 0,7.5 2,4 Z"
+ style="fill:url(#linearGradient38885)"
+ id="path38309"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38315">
+ <path
+ d="m -2.5,4.5 a 4,4 0 0 0 5,0 z"
+ style="fill:url(#linearGradient38887)"
+ id="path38313"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38319">
+ <path
+ d="M -2,4 A 3,3 0 0 0 2,4 V -6.5 a 3.8,3.8 0 0 1 -4,0 z"
+ style="fill:url(#hold)"
+ id="path38317"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="rotate(45)"
+ id="g38323">
+ <path
+ d="M -1,7 0,8.5 1,7 a 2,2 0 0 1 -2,0 z"
+ style="fill:#3d2c0c"
+ id="path38321"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer6"
+ inkscape:label="Layer 2"
+ style="display:inline"
+ sodipodi:insensitive="true">
+ <g
+ id="layer3"
+ inkscape:label="Layer 4"
+ transform="matrix(1.0666804,0,0,1.0666446,0.0428691,-0.00298246)">
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:2.24979424;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 15.667684,15.742702 c -3.40903,-2.920537 -4.694046,-5.522759 -4.694046,-5.522759"
+ id="path4586"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ff0000;stroke-width:2.15561461;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
+ d="m 15.747607,10.026563 -5.09709,5.759107"
+ id="path5174"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cc" />
+ </g>
+ </g>
+</svg>
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..76e7e9ec79177e88c197e5874a8f4cc80ee69923
GIT binary patch
literal 3461
zc$@)+4SMp4P)<h;3K|Lk000e1NJLTq0015U0015c1^@s6J20-I000W3X+uL$Nkc;*
zP;zf(X>4Tx07%EJmUmDTc^1ci-IEhA3|YdElOUM^B*!5Ll0j648Nv{TabN%u6SBAh
zD~gCDWknZ36kL@cYd{PbSOgR>?;wkcaxMl$MdcbehP%4jKd$Ot*X#cDtJmLt9e!0`
z0LXnTK0g_j0w9H#E(r8=p@&CA(s6$P2-!%$4j8f6X?*8^0AKL8@o@<0MdGf}oEG@s
zg35_aV*>yMARNO<W2Yc|7Rh!tUyu#}YEDlxGo3%jdk~fpAOj+NbdKW}xP6Xe7Px1=
zCpgd*VG#g$GK(dM2Y@tz)ae=QctnRxM%aYM;qnk(iLe7ZfyF^M2jS%@DXDW@kFZ|M
zzv;yPhh~gelf{Y`^TM;t2i}dF#!qH#1pn_yNfs{NLjy^2g1{>fIU-*Ws!vMw5i>7l
zmH#4hIrIJVoFMcHS!8yaYvdwxSZ+RxEKCY<USyVF=~`|&GkB2&sexks*fjSbv3@Mm
zS3E=VDlx}#J(!D}oe&(h$Qj(wRg0XK6y&qi<0`HTgn{Cj;{+b!XG}?3nvKm`njt+Q
z*lTIFaPbVBST}cZofjhR=cl`fHTlT_;{MoVPjNjhBS?I0I`V@paw5w+U`Z!H{C+?X
z@B=Qu6xaX@U;@I@W3$rdhILKlZxnFj6VmC<$ZCnDGkI*|<#aO>Q%eBm*51OWeSd#`
zO+j+_r5Ybe8!GY-b}iLntO01N1VE9xRMV;fKoWUht&wb@AY-A1p4(vn5l8|Vpa@if
zCeQ;$zzkR+YupLA18?9Df<XjW2iPD1qyPcP1UX<k$OnaB57-YXK@B(p>cL5H8nl7)
z;4-)lZi7BB2p$0u7z3}tB=`tsAPB-i6o>{XLTZpUWC)o-49Ed;hkT(xC<2Ox;vpU+
zgmR!AP$5(TRYHfM2IyDl9CR7F3H3t{pb=;sdIx=hQ7{>nfmL7~cm>RWonaq17+wp<
z!yDjiI1k<pm&3L23Ah!$4Bv(aVG;Zqo<ac>2_=V8M;W0QC|8s}DiRfk5}>xA3Q%RJ
zT2vFN1J#YXixQzGP@mCQG!0Eh8=>vcUg!`s8@&O&1zm)$MAxHR(O1!T(Ie<d^mhye
zql_`c*kYKNa7;WV6O)fA!_;9~FxN1Hm@&*pEEX$=)x}z4y|5A3MC>MP5%wUq8QY2N
z$BtsBa5$VI&Jbsh^T)Ap>9~Ac1?~jy5^exDhWmsk;nnbFcn^Feo`=uHm*J1&FX9LA
zFYq%2DS|e^mf%m|5H=Eu2}cPXggb;W!VHm0)Fs*zgNcd6?Zk3o6Y(1HG4VZ#MA9JH
zkXDnpq-~^f(kapn(g^7@S(<D>b|puVh2-7jW8_Zq1M+(cg`z`oqC`^CDZ42Rlq-~{
zlur^g31bPS1V>`4M3qFV#DK&bNrI%dq_gBY$!y6o$up97BqyW@QaVzuQY@)0QdLst
zq=u!Ys5Gi6)t{O|EutQ$-lV>i#!2f)yGwJV^Q4bRUy&ZAp=g>kR~nnPgLag5jW#BO
zmC==9$|TAZ%AAzxm6?>4mbH)#mCcf^lD!}+l0(Vq$T8)T<%;D_%MHnWkynv-mXDR+
zCEp}JAV00JOu<PZR-r(lSz%D&iy~doT`@^<uVS0x6D72gfzoQFOr=9g*OVrf$t<&5
z#$Hyi?DVpS%CNG5a-i}i<s-^H%2O(;DxNBQl?s(BDif-5s!pnjs->zIRmat6Y7S~#
zwNkZ9YOm<BbZ2@py_|lPKB=y(?xikNKdj!XKBJ+l5u%Z+(X8=Alc>qijMFUD?9!an
zQq%I&%F$}ndZ<m%W@sm9AJFdBp4QRT3DeoB)28!MS3#Glo2}cZ`&f^n=cu<quU7A#
zK31QhpQL|Kzs~?Rurx?8s4}=?2pd`%at#j}_AkdQw^`0xUb}qQh-~C+lws6pG-51g
z>}#BB+-Ce{h1QD56?<27ulR0aX_9Pm)Z~$=w5hjgu4#wqJ2L|_wpo?gJ#&h=hxu0X
zcJsFuh8D3Fhb-<}(k%TfcUpE?ezUT+5?D1^jazG3M_X504>M#K0gNKX4I7M&i_KP>
z^ERJtt!&e6Puu=wXJnUTciisfO5K&QE03-mwb!s`*&ntSInW)V9e#EYIjTFd9BUn)
zIcYj^oa&rjIO{tnI-hWU<6`0>aA|RwcC~fg?Aqyua`SX6aO-uKb`No{bbsoh;SulA
z=rQSO>AA_X(+lI}<F(gon5n{KGaHzb-qzlmy|4O^eS&-{eV+Lm_zHYG{9r$CzkPm>
zS81=}t!ncJ{!IUU{!apQ12zQwwi>%SV0G2%=YghyIf30lw4n7t$Adlwy95^pKMK(c
z5r%YyN`^*-HiS-xxrLR4iNaTeZw|j5p%lT5Xph82hDO#!POWiYvwzK4lx0+Y)X-Yp
zwOMO#u2Wi<yzat!$@Q%DXQDCDVbRB<zq0&UwXCTaW=wU=B-@pJfc=`|#3|*B$J)o1
z#J-5Lk1L6L8E+rIFMd41F`+DBg6qny<i1VxN~}qoPV!GWmNc6jmfW0zPl--B$CKtI
z@vf$-re>w~^9}j=e9;Em4f{7t3VZ~0X;9kQwDxov#N_k{^@KkNM=~5Tsxm%hhGw40
zqGs{3dNvwt+_mvVwnz3ao6wuso4Rt;bGGM<Y<Av!cnh>8W=q#r&8>M`$F_NHtKUxC
zp0xe<T$9|A+z&g#c68*a<Za6v{lW8x#(ar<LH_VgyPY*ZqJQN6*t^SoSH-UH1)PFg
zg(ih%g<p%<MYn!3{i*z?+1+uwdyB1#5ADJ3;q4jP>$tbRgj$kaGP=)iUu!A7w4n6E
z{`LEBmRXe593UPL9uSrLmbX`ERqUzwTA5HeSmjdHT&-Nat9tq%=itC0r$bFODm8^Q
zUw-EPeE+cL;nrH6+Oi|qBN<1Y9}PX){R`ulhB~FX!n$wAc*jKbf%R7#tQ#7RD<3az
zgd2s8<0qm{^qq7&+1_N-bhufrx$qP?B|P=&*XUn|PWznhI>R{AbXNOpbqlSfpcQJ}
z*gDyk&?ah+XzxGgeXgs+uA}8Q<KK>*S3h5MLH0uNMdHQ0i?f%qFHLpwJ0~v3UmopR
z-}UH9_?3HCS6}VD=5y`Fb&u;;x}Cc(-LSuL{-*8CbGI0`+HPCjZtbz`Y5Cpq_m*C(
z-qt(TciQ`G`a1eo_Fo)u9JqYf?e6t^%zL*7R}J<Lg$&&vjv5x-=iGn!Ao;=DhZzrN
z9&LM!d0g;B>PgvC<)^iO=>O3yvJzbwaU1D*7WC}VD0}qvpJ{)79m{)8d0zHH?M1^&
zvzO<`J;wW9t$8)}n*VxcBL5%Me^kHGdvo?Lr@wk8!zahy3f|7XD|)Z^{@4eL4_#9M
zQ%^spe4Lps_@wx${xjoq_m{9QFJ>~o;=Y!D)Bo1--RJw`*_7E?K1;xww*n|6;kY;e
z-W>yg5&;0M4*;UXf;XNIXu;3V6Zkt`@XzyM1;CL`0D_$W@I{Ul+W^o(QW7~2aGI~f
z=H}x3SER+6n=NRO+7N3}Haq(s@q{>i0KN^+&d#*W&VH*yhJ6S?=f?$4J{P`50ATlr
zGHl#$@QVJ+8~+o2YXM}Bs!>h=000JJOGiWi{{a60|De66lK=n!32;bRa{vGf6951U
z69E94oEQKA00(qQO+^Ra3l0nh9fb98LjV8*V@X6oR7l6YmoaGKP#ng;XHFcd6|@}9
zBISm5=psEgT@(skI^?DfX6kT)aO*+0l7cQBg2Nq$V_Z)c5nM`3p%mPTR-xDy2OT14
zC@EUgcatISl-_Zd<Qu;{_~rlq-pl)ksH%#;(PT0~wOXZ@mzQX@T6lPPz<4}HzuzbL
z2fh2#g27<$GoQ~3l}ZIqPfxH*Z-P%xPiL|$4|QFK-C7EsPNxtAfnHx<1HgMYAkQ*r
zJRYAz8TouZ92^{=R4Sp<>3mUDmDpDC?(QzVzrSZZLI^}rgd|C1Wo5<wRv#T5(Pp#B
zEb#mNP!t7;M1t7aYBE1gy<V@ev$OM&Qp!LtE-uQENaT}a8E3QEO)HcT0!2~a1k?oU
zx{j->tAO=^D2leXcL6NRGPPFg^Z6i2l4DR4T&vX>QxF8$wR_(LKR!M(=KlUZoR*of
zRk2v?vnyb0z5oyohkv*N_Ika{*>yu600;(yjHzjwJK&9t4aU5=xoNlpjz*)5d3JWT
z=?XZROfqJ@UJuM>Gv~pnREmT`A=7+$c|oyQq^|7h!^1<<q?DpwucO=TW*h|rKqiwR
zYin!f%|t0hp-}kt{QT@JI2a5fm&+}Tw6ZMI;c%FDbmkHSfuz%Ev+DT_RxX#n-rnBY
zn)$-ab$onG48x%9cALrk?(Pn{uG6ipEn>&lZDGpfI1VQ#CuDDLkEwi~`)D-!ZWxAr
zGuJXT8V!1WevZ}ERq#BI_4Re|JP)4diO1t{?oT2Xi;?Z^ZM0e~I)B06Z!$+Jw(9lu
n6`H1f0DwRs(B(Mpzreo$fyw&*J0h@i00000NkvXXu0mjf3onty
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..7fe7fb542f47344bf71b319f326f617b43fc56b0
GIT binary patch
literal 235
zc%17D@N?(olHy`uVBq!ia0vp^oFL4?3?zm2ODh8@mUKs7M+Sy#H?H_c7y<c`C9V-A
z!TD(=<%vb942~)JNvR5+xryniL8*x;m4zo$ZGei@1AIbUV`C0M5K!Rc$&**FUVZT3
z!HX9!-o1PG>C>klKYslE`}hC<|5-uYzkn*3OM?7@Q`67zu<UyW<XU^WIEHY@CMN|2
z1tle1irShrl}Y&U-aYI*4xi*F{MrBOdllQVdM}?Mhfjb1mpcfmDp#^CGq3*k<|hNg
X?~|gl_b<`_xt_t()z4*}Q$iB}{##qC
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..df66d771c347df2fa658659e0c0882ba2fe0d39d
GIT binary patch
literal 232
zc%17D@N?(olHy`uVBq!ia0vp^oFL4?3?zm2ODh8@mUKs7M+Sy#H?H_c7y<c`C9V-A
z!TD(=<%vb942~)JNvR5+xryniL8*x;m4zo$ZGei@1AIbUV`C0M5K!Rc$&**FUVZT3
z!HX9!-o1PG>C>klKYslE`}hC<|5-uYzkn*3OM?7@Q`67zu<UyW<XU*TIEHXUCnp63
z1tle1irShrl}Y&U-fDh{1yBB8U47ly;BRoyB<4x)@9$^s($G9<Y_Rv{=JfOI42%_`
Vv){atuLGLF;OXk;vd$@?2>|rJS}*_r
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..561262f247bc25705b42eda6071f96d9aaff1024
GIT binary patch
literal 3833
zc$@+G4hHdwP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV000aUX+uL$Nkc;*
zP;zf(X>4Tx07%EJmv>at$ri`wS5gQGEf4`Ip*KN#mEJqj!A3{|gc3qRP_d(n3a(v5
zu%fFVimnBawPL}72-xd_h>Ef<7Hp`zNdo9|&;Iewd2`PE^2wch?|f%|GruzffTBH1
zAjn3Q0LbCz3!?*ln2AZrOwuDj1qdJmHDJPGiv-?LQDNX;%lli<7nbXml3d__E6N<M
zhz$S|1v!-?V&_0U0syYb76|hJAhbif!OVPtgx^6{6+(dk5Xllwm$8<FQ)O%|^@)x4
zh3p3anaW}b(*aPB<YyMJ(_tP30c2}FhsTG!9kK^IgT;Z|1$jbFPA+6cDrD2tAM&LC
zSI*S2oGe!Q7|X^YEo48QNRZ8%4gTLHCtEz)hd!*-3}H~TWXy2hs<U!KB@DGA$N8xd
zk&sm&U*vJ5{t~{IAr6Wg@$YAgeBqn{1^_7Fu>3+H!?{Em;;cCD5w>LsrLoW-jo{@6
z$BuBSFgJQ6KU%;Q`NxcM1~)itB<C`IcEl)eP2&XwLxw92JyMtvn;_GPo-5$RM?hA9
z+%C$B2_5l&RG8r#G3p}}M@uvtLVlMf42YKbVCp%d(Q#o`Y?f4mI%Kc>jMyMqESAU>
zC5DY^<8b}_Wg0LJmmfEx8H20KCwe5dSdbkx;*V|RW(P*g<{3LEDu@}4?aYS@Og0bL
z<4jgaluSSNUXULZE7OMKAPo2dKfnZHSW`hR$OOE${)T?|Pu3CuSU?EU0T)<~SVkif
zq%C~-i~)t<FGyS<8jJIhwsJuMw0|A5%AS@WP1;%@jmQE|A?M(lxCicrhry>8thTr_
z?lKz3R2=P<=uh+`1`z{@W@9?oP<1w}Lcsf{?RzBQDWQY#m~fNOMYsz>VJjDoS)!0X
zHnMo|M4Dyf{|sJ{@F(R=5DvOvT)@caQekx8s1I%oRXgB5xI5Gjwc`w26IkN*a3tQi
zCmfptwEn12Jf`CNT>jjjMBk5N9I-2yD>#g(NFCGfJLdIs7T+J{hy#B^fA++dVTZ96
z><o4ZJB~E~CUy#I#9FZv5*}L#Poyhhw6|!fwk(*3H|k?uYu#u4<sZGXMidC8?;GTE
zXXQ&agm123wvd;ek<avoUt}&bn9sJFz_hitu?Il%eV2Vc-ZG@$ZG>_8yDjfD04^oa
znEu_yG63M%Qvm4izuOGn!0ojffD`R(v9Lg9!zB*_pa4a<Gc<q>FaV}-Ot!!gxB)NV
z4?;jBhy_VtI-K<kkONiE1arY6Pz*}JYOn!RfNfwWs0RDNA<zJhgEQa)xB}Y2O>iHs
zjb88)41o7w2tg1MLPO|?2BM1?Ar^=&;*5A8{zw=SjU*u{NIJqt#K>G^F;a@GMJkXT
zNDcBkatt|xTtYgKyGS?EhrB^PqZmp>RZ(r!7@deZquyvJ8jDUt)6qP%5M6?<M9a}C
zbRSxeo<XmmH_<NiB|3-!OdexkdYC2VjQL`bSTdG|39<QD308(xVRcv&eAm~pF03E>
zgcET(&crR@`V7M3a5kQY&&OBb6?ipXkDtS@<K6fG{*6E*Xb~(3u7qGhA|ahHlTb`3
zBkU#|C7g#ls+aJdNF*|dCPXK=H<O5u$j#4W@F#M8tMVh?eUL?UUBEJ*I8ND_;b
zPbwyDBJCrcB6X5_Ngv7bWL>f?Ie?r@=97!aW#ql&ljKhFGxCs}lAMv8t6ZcUM{c&<
zD!E;9O>)=ddgX>F$`lidJ0+HqNm)cGr_@o-Qyx&>%FD~^%e%-=mgmVYlrNV*D1TAD
zTmBPOg=$XqrB0=asVk{_sHdrSsc&gCnla6bmQ2g1t)$h^&e9&yJ}S@^tQ3M3I0_3C
zDi!J#Iu%|i$|)KtdMi#>EL1F0Jgj(4v0q6}$ymu(iKR4OsZ!~f(ru+dWxBGBa-?#O
z@(Sg>%9oU%tB_QTRs2;rDoa#$s+?8np<{Fdx-XqgUrgUcKTq#fC90aJ2CHVOma6Vo
zZBrdkQ&F>5i&vYaR;hMEt&4#%j2Xd<Y{n|aA;vAn=W*KOyvK3JEgx4mu5;W6_3`Rn
z>Rj~_^#kfR)jw-6HT*TQG}dS|Xgt)!HO)0AYtGc%s(D_sf4u5=xAE-pCF2i`zo&(2
znQKLB&C%Ma)v7h9t)m^NEzsVieO9|)hoR%8lc}>_=cG=bE?w6{m#4d4_mu7nrW(_m
znawO`o?{N^Y3T*&iS>5qwdoD%o9M^s7wI3+e`r84a5hLYSZ~m5Fkq-_7;ZS%u-5RN
zk(`mUQHIe*qZXqN#wNxI#!HPGjGvpRn*^H_n$(&+Fr}G#n&z2SncgrXnmL<gnQbv^
zH%HAK%rnd@&97OY7LFD?i!Bx%69^MrCh#XzO}K4Iwe+?uu&lN0wqjU?Sru6|SiPEP
zI5BzRnu#qFzgat2XIt;GzHg&q6Jk?j(`56;*4&nDTWNd4j%F8Nx4^E^?zO#zJ=cD#
z{T&CoL%73Ihh~Sbj!uq3$2!M8Cqt(cr_D~comHKqoL4wsbRoF-xGZov?()gi(KX-o
zkn7)WmTp;Yd)=N-GMU7kv};n2yS_WieTRFO2h$_PW4lL}r=BOvv&!?amw^|@Yq!@k
zZ!_;q?|t5{e5`$hK1Y2%_`3Se^F8f{`33rw__g_~_{aHI_&*BJ4@eKF4HyV?2%Hmm
zI*1Sy8nh<pcCdCZJGdrzAjBzTUdXvnYG_PoMd;%&^DtppQ#cwP6230{L4;8RKcXQL
zL<UE$i+mVm5|tPA$7JH<sL7R+pGMn6&y8-0p~uXK*&Fjd)+csV?EN^?IB{Haykh*+
z_?q|+34RG{6S@*7CeBN|lBAi$OKMCeCC4Z4P9B`%KV`#|-l+~#i>Kb2W-@Kov`f=9
zr)N(;F+*_%YsTReLP|o)-jpw_NLCeVFf}-JbLs%wm%WkwlH<i$&*|g3bJud8rMaiA
zO?#g1p1v-<FT*pVETf<2%d6nM&J4=jmiZwoGHXxPaCSoWfgEy9O3pF93O|c~E>|aa
zR&IyDLQpK|%5%-zkT)O<748-xqG_VWd{ubVX%(A_e-U>Vcob9?e4H6S^YAR?S^Qb8
zv(0BOpZ%;bpzyaj_#F0}=DB)v7tQUS=RI%7d}My={N@D)3zjVCT^P8qdJ$z&)}pIL
z)<tWJ-Yrg8+_Xe{$-*T)zXbkLTdYtlEWW+eZRxgU_%hzIw&ixqH!c5G!YR32YF%1Z
z`eg-s#pPdZel7oXcxBqkwpEU+wyq|w=C8iB#&b>eTIIEcYkSs(ud83rTwk*O-G&((
zE|uArZQDrMDBjpr9#-DC$#B!^O<yW9DsEQ#R36x@wR!pG4_i1}I=6alt=p!(t#sSx
z?Y!-Gb_DLIuQIMG+ezG6u=DAz_+2f(IsaC(TXT2m?yr0Jd%CKltIyRq)zs|O+PkV2
ztrgey?VGx<eZSxS#yZQo9S0Z(N)LjA;)5@KPx<}Uq0mFkhn)}C9Wgqx`6&HpNj*|O
zyMCY{qoJ!Ysj=f&$g$=ox2B_iSpBi*xZd%~6KW?`our&xa&q`o;i<vXg46wH($DlX
z&uG4XHu3C@bCb`toew>K@j}3bvn}2&r!Kl*Jbuab(y`0Vmm97)Ua4<&Xgzw>;p)*g
z$F}-wPS+aSUD}&ECUu<b^z1xy-S7H^8^JfO+>E%{aVzfDo!e7ycirLKd44zh?(2I6
z_lE8-d_Z_m@=)nv*(0q-RezfOd7#U&>qNI-ckAPr$M<{KJujb#o_y(D@|5<p?3wPf
zn&-C9kM{-iwZEA1qW7iX<xqd|U&?=Ner5XV$lqRnw+<u@^u89p9)7dpt;X9u@9f_-
z4@M0>dY|)t=tIdzjgQrzoIkaEPWb$6XyzBvm-4S>Uz@&#etR&SGdwI{30cyk00t|X
zmIlC^Jpj;>;MoD*<0uNU`<JvJvOA-cz`wEVjwUUR0POJsAZ{T5VepKy6;>n2RQM}7
ze#d$N)Xr|KjucUvovkbv72aY9ABTtEY6C!O0^qA~cz9^b@bK4NQ0!d*PUp$)eUd_i
zXZ-EbbIw(RrT@5p{RjROYWNk`t^NQ200v@9M??Vs0RI60puMM)00009a7bBm000XU
z000XU0RWnu7ytkO2XskIMF-vt9S;N&AL>TU0007LNkl<ZILnoiUrfzm7{@=q-|rli
z$((a=bo^^M)J6UiE{fB3Hnuj)(rD6!=HHbY)?pZ%M$u-L3t<eiVP<Syl$FI2^QU7>
zG!tT{qeD(~aL(_2Tu3U-c&?uJ-S_=|_I=(5hN!McBLGTHWJ<Ks6$%~VzsnSv<lCDr
zjSq630DS${{BI9MIsy3Hlm93oYq8{Rbc>Q}MgK)tQtr;%kgCpKohFmA(&h0p&le>R
zEgivq|7Oq`UKPN-R7_bQ{-;wUZP^qFcl(wPrPcqWSLSXmX>wja`5bS-2kt!3v6@y|
zUesR^2_^P~s{c-Jx}WJ!@=O;~7D=4PE9<r;%j@=qk}%UNrmQR;thfAX-glJZ7%Rm~
z*Bqa5w$y^IMl)7_i+zM*Vv;a7^(8eoY*iw1PSdo=9}&hlw^P?}dC$9#R<biD05NQF
z3cF1M6qhP(6zqyevG%Bl7<~p2vju>t>?!e8gk7ihE8sJrNF4bcm+L3PY1dh?=nbWZ
z{c8Yi(BN(+UaX!I^{$>OVtPc(`6A|gx!as9!OnSo=Rhz+;xn_wl)}0J5yCwe)^92s
zX{kD3^wI%_v7_8dZCzI%$muZHSJ=iD-|Z~<zS9YyE2&SZZ>Txwo&Ac*;~fL$d#iOC
zH}6ZoyBI}zc8=7h7@wlAuK{Rn?Q6@D^8;I3&%7Z%e%Qchstet)f&4Zx3@cTqyZIRD
zregPwB^G~6+g3+H53@GgFiZnQ3{BI47%;BiZ^r-&3eXu2F*~d7-pkiMgiCUQfpD5G
vpfsRY0-$SxQu?6P6%-nA`Y6YEH^+Ve(rpoUN66bS00000NkvXXu0mjfGssP2